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 abd4c4380e43 CAMEL-24333: Add structured tool output (outputSchema /
structuredContent)
abd4c4380e43 is described below
commit abd4c4380e43359988c4943b3a1759170a9ff639
Author: Omar Atie <[email protected]>
AuthorDate: Wed Aug 12 01:21:58 2026 -0700
CAMEL-24333: Add structured tool output (outputSchema / structuredContent)
Extend ai-tool with optional outputParameter.* and outputSchema metadata,
parse JSON route bodies into AiToolResult.Success.structuredContent, and
map through the MCP bridge to Tool.outputSchema and
CallToolResult.structuredContent.
Closes #25398
Co-authored-by: Cursor Agent <[email protected]>
---
.../apache/camel/catalog/components/ai-tool.json | 28 +++--
.../camel/catalog/docs/ai-tool-component.adoc | 44 +++++++
.../ai/tool/AiToolComponentConfigurer.java | 14 +++
.../ai/tool/AiToolConfigurationConfigurer.java | 14 +++
.../ai/tool/AiToolEndpointConfigurer.java | 14 +++
.../ai/tool/AiToolEndpointUriFactory.java | 7 +-
.../apache/camel/component/ai/tool/ai-tool.json | 28 +++--
.../src/main/docs/ai-tool-component.adoc | 44 +++++++
.../camel/component/ai/tool/AiToolComponent.java | 6 +
.../component/ai/tool/AiToolConfiguration.java | 34 +++++
.../camel/component/ai/tool/AiToolConsumer.java | 15 ++-
.../camel/component/ai/tool/AiToolExecutor.java | 19 ++-
.../component/ai/tool/AiToolParameterHelper.java | 95 +++++++++++++-
.../camel/component/ai/tool/AiToolResult.java | 9 +-
.../apache/camel/component/ai/tool/AiToolSpec.java | 25 +++-
.../ai/tool/AiToolEndpointLifecycleTest.java | 53 ++++++++
.../component/ai/tool/AiToolExecutorTest.java | 82 +++++++++++-
.../ai/tool/AiToolParameterHelperTest.java | 72 +++++++++++
.../ai/tool/AiToolRegistryListenerTest.java | 3 +-
.../component/ai/tool/AiToolRegistryTest.java | 50 ++++----
.../resources/output-schemas/weather-result.json | 9 ++
.../agent/AiToolSpecToLangChain4jTest.java | 20 +--
.../component/mcp/server/McpServerBridge.java | 7 +-
.../camel/component/mcp/server/McpServerTool.java | 9 ++
.../component/mcp/server/McpToolCallResult.java | 13 +-
.../component/mcp/server/McpServerBridgeTest.java | 21 ++++
.../mcp/server/vertx/VertxMcpServerEngine.java | 12 +-
.../vertx/VertxMcpServerOutputSchemaTest.java | 138 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 12 ++
.../dsl/AiToolComponentBuilderFactory.java | 43 +++++++
.../endpoint/dsl/AiToolEndpointBuilderFactory.java | 64 ++++++++++
31 files changed, 922 insertions(+), 82 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
index e758755891cc..778564ec6a39 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
@@ -31,11 +31,13 @@
"destructiveHint": { "index": 4, "kind": "property", "displayName":
"Destructive Hint", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool may perform destructive
o [...]
"idempotentHint": { "index": 5, "kind": "property", "displayName":
"Idempotent Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that repeating the tool call with the
sam [...]
"openWorldHint": { "index": 6, "kind": "property", "displayName": "Open
World Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool interacts with external
syst [...]
- "parameters": { "index": 7, "kind": "property", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool in [...]
- "readOnlyHint": { "index": 8, "kind": "property", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not m [...]
- "tags": { "index": 9, "kind": "property", "displayName": "Tags", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags to [...]
- "title": { "index": 10, "kind": "property", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
- "autowiredEnabled": { "index": 11, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
+ "outputParameters": { "index": 7, "kind": "property", "displayName":
"Output Parameters", "group": "consumer", "label": "consumer", "required":
false, "type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "outputParameter.", "multiValue": true,
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "des [...]
+ "outputSchema": { "index": 8, "kind": "property", "displayName": "Output
Schema", "group": "consumer", "label": "consumer", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"supportFileReference": true, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Raw JSON Schema describing the tool's
structured [...]
+ "parameters": { "index": 9, "kind": "property", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool in [...]
+ "readOnlyHint": { "index": 10, "kind": "property", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not [...]
+ "tags": { "index": 11, "kind": "property", "displayName": "Tags", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags t [...]
+ "title": { "index": 12, "kind": "property", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
+ "autowiredEnabled": { "index": 13, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
},
"properties": {
"toolName": { "index": 0, "kind": "path", "displayName": "Tool Name",
"group": "consumer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The tool name. This is the
name the LLM sees and uses to invoke the tool." },
@@ -44,12 +46,14 @@
"destructiveHint": { "index": 3, "kind": "parameter", "displayName":
"Destructive Hint", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool may perform destructive
[...]
"idempotentHint": { "index": 4, "kind": "parameter", "displayName":
"Idempotent Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that repeating the tool call with the
sa [...]
"openWorldHint": { "index": 5, "kind": "parameter", "displayName": "Open
World Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool interacts with external
sys [...]
- "parameters": { "index": 6, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool i [...]
- "readOnlyHint": { "index": 7, "kind": "parameter", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not [...]
- "tags": { "index": 8, "kind": "parameter", "displayName": "Tags", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags t [...]
- "title": { "index": 9, "kind": "parameter", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
- "bridgeErrorHandler": { "index": 10, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
- "exceptionHandler": { "index": 11, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By de [...]
- "exchangePattern": { "index": 12, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." }
+ "outputParameters": { "index": 6, "kind": "parameter", "displayName":
"Output Parameters", "group": "consumer", "label": "consumer", "required":
false, "type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "outputParameter.", "multiValue": true,
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "de [...]
+ "outputSchema": { "index": 7, "kind": "parameter", "displayName": "Output
Schema", "group": "consumer", "label": "consumer", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"supportFileReference": true, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Raw JSON Schema describing the tool's
structured [...]
+ "parameters": { "index": 8, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool i [...]
+ "readOnlyHint": { "index": 9, "kind": "parameter", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not [...]
+ "tags": { "index": 10, "kind": "parameter", "displayName": "Tags",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags [...]
+ "title": { "index": 11, "kind": "parameter", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
+ "bridgeErrorHandler": { "index": 12, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
+ "exceptionHandler": { "index": 13, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By de [...]
+ "exchangePattern": { "index": 14, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ai-tool-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ai-tool-component.adoc
index defb3f0a929c..c13661511ada 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ai-tool-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ai-tool-component.adoc
@@ -197,6 +197,50 @@ Top-level schema properties are exposed as exchange
headers. Nested values are p
The root schema must be a JSON Schema object with a top-level `properties`
map. Camel always allowlists top-level property names when invoking the tool
route, even if the schema sets `additionalProperties: true`. Nested fields are
not flattened into headers; only top-level properties become exchange headers
(Map/List/primitive values).
+=== Structured Tool Output (outputSchema)
+
+MCP tools can declare an `outputSchema` and return `structuredContent` (typed
JSON) so clients parse tool results reliably instead of re-interpreting free
text. Use `outputParameter.*` for flat field definitions or `outputSchema` for
raw JSON Schema (mirroring the input-side `parameter.*` / `argSchema` pattern).
The two options are mutually exclusive.
+
+When an output schema is declared, the route body must be JSON (a JSON string,
`Map`, or `List`). Camel parses it into structured content and forwards it
through the MCP bridge as `CallToolResult.structuredContent`. The text
representation remains available for LLM adapters (LangChain4j, Spring AI) that
consume string tool results.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("ai-tool:getWeather?tags=weather&description=Get weather"
+ + "&outputSchema=classpath:schemas/weather-result.json")
+ .process(exchange -> exchange.getMessage().setBody(
+ "{\"temperature\":21.5,\"unit\":\"celsius\"}"));
+----
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: ai-tool:getWeather
+ parameters:
+ tags: weather
+ description: "Get weather"
+ outputParameter.temperature: number
+ outputParameter.unit: string
+ steps:
+ - setBody:
+ constant: '{"temperature":21.5,"unit":"celsius"}'
+----
+====
+
+Flat `outputParameter.*` options use the same sub-option syntax as input
`parameter.*`:
+
+* `outputParameter.NAME=TYPE` — field type (`string`, `integer`, `number`,
`boolean`)
+* `outputParameter.NAME.description=TEXT` — field description in the generated
JSON Schema
+* `outputParameter.NAME.required=true` — marks the field as required in the
output schema
+* `outputParameter.NAME.enum=val1,val2` — restricts allowed values
+
+`outputSchema` supports Camel resource references such as
`classpath:schemas/weather-result.json`.
+
=== MCP Tool Annotation Hints
When exposing `ai-tool` routes through the MCP Server component, you can
declare optional behavioral hints aligned with the MCP specification
(`ToolAnnotations`). MCP clients may use these hints for per-tool policy (for
example auto-approving read-only tools or requiring confirmation before
destructive ones).
diff --git
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolComponentConfigurer.java
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolComponentConfigurer.java
index f5a2d7183da4..bf7b00f520bd 100644
---
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolComponentConfigurer.java
+++
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolComponentConfigurer.java
@@ -44,6 +44,10 @@ public class AiToolComponentConfigurer extends
PropertyConfigurerSupport impleme
case "idempotentHint":
getOrCreateConfiguration(target).setIdempotentHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
case "openworldhint":
case "openWorldHint":
getOrCreateConfiguration(target).setOpenWorldHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
+ case "outputparameters":
+ case "outputParameters":
getOrCreateConfiguration(target).setOutputParameters(property(camelContext,
java.util.Map.class, value)); return true;
+ case "outputschema":
+ case "outputSchema":
getOrCreateConfiguration(target).setOutputSchema(property(camelContext,
java.lang.String.class, value)); return true;
case "parameters":
getOrCreateConfiguration(target).setParameters(property(camelContext,
java.util.Map.class, value)); return true;
case "readonlyhint":
case "readOnlyHint":
getOrCreateConfiguration(target).setReadOnlyHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
@@ -70,6 +74,10 @@ public class AiToolComponentConfigurer extends
PropertyConfigurerSupport impleme
case "idempotentHint": return java.lang.Boolean.class;
case "openworldhint":
case "openWorldHint": return java.lang.Boolean.class;
+ case "outputparameters":
+ case "outputParameters": return java.util.Map.class;
+ case "outputschema":
+ case "outputSchema": return java.lang.String.class;
case "parameters": return java.util.Map.class;
case "readonlyhint":
case "readOnlyHint": return java.lang.Boolean.class;
@@ -97,6 +105,10 @@ public class AiToolComponentConfigurer extends
PropertyConfigurerSupport impleme
case "idempotentHint": return
getOrCreateConfiguration(target).getIdempotentHint();
case "openworldhint":
case "openWorldHint": return
getOrCreateConfiguration(target).getOpenWorldHint();
+ case "outputparameters":
+ case "outputParameters": return
getOrCreateConfiguration(target).getOutputParameters();
+ case "outputschema":
+ case "outputSchema": return
getOrCreateConfiguration(target).getOutputSchema();
case "parameters": return
getOrCreateConfiguration(target).getParameters();
case "readonlyhint":
case "readOnlyHint": return
getOrCreateConfiguration(target).getReadOnlyHint();
@@ -109,6 +121,8 @@ public class AiToolComponentConfigurer extends
PropertyConfigurerSupport impleme
@Override
public Object getCollectionValueType(Object target, String name, boolean
ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "outputparameters":
+ case "outputParameters": return java.lang.String.class;
case "parameters": return java.lang.String.class;
default: return null;
}
diff --git
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolConfigurationConfigurer.java
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolConfigurationConfigurer.java
index d20e0c4742a1..487f382afa7a 100644
---
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolConfigurationConfigurer.java
+++
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolConfigurationConfigurer.java
@@ -32,6 +32,10 @@ public class AiToolConfigurationConfigurer extends
org.apache.camel.support.comp
case "idempotentHint": target.setIdempotentHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
case "openworldhint":
case "openWorldHint": target.setOpenWorldHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
+ case "outputparameters":
+ case "outputParameters":
target.setOutputParameters(property(camelContext, java.util.Map.class, value));
return true;
+ case "outputschema":
+ case "outputSchema": target.setOutputSchema(property(camelContext,
java.lang.String.class, value)); return true;
case "parameters": target.setParameters(property(camelContext,
java.util.Map.class, value)); return true;
case "readonlyhint":
case "readOnlyHint": target.setReadOnlyHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
@@ -53,6 +57,10 @@ public class AiToolConfigurationConfigurer extends
org.apache.camel.support.comp
case "idempotentHint": return java.lang.Boolean.class;
case "openworldhint":
case "openWorldHint": return java.lang.Boolean.class;
+ case "outputparameters":
+ case "outputParameters": return java.util.Map.class;
+ case "outputschema":
+ case "outputSchema": return java.lang.String.class;
case "parameters": return java.util.Map.class;
case "readonlyhint":
case "readOnlyHint": return java.lang.Boolean.class;
@@ -75,6 +83,10 @@ public class AiToolConfigurationConfigurer extends
org.apache.camel.support.comp
case "idempotentHint": return target.getIdempotentHint();
case "openworldhint":
case "openWorldHint": return target.getOpenWorldHint();
+ case "outputparameters":
+ case "outputParameters": return target.getOutputParameters();
+ case "outputschema":
+ case "outputSchema": return target.getOutputSchema();
case "parameters": return target.getParameters();
case "readonlyhint":
case "readOnlyHint": return target.getReadOnlyHint();
@@ -87,6 +99,8 @@ public class AiToolConfigurationConfigurer extends
org.apache.camel.support.comp
@Override
public Object getCollectionValueType(Object target, String name, boolean
ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "outputparameters":
+ case "outputParameters": return java.lang.String.class;
case "parameters": return java.lang.String.class;
default: return null;
}
diff --git
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointConfigurer.java
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointConfigurer.java
index d369d659852a..51216e1a274b 100644
---
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointConfigurer.java
+++
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointConfigurer.java
@@ -38,6 +38,10 @@ public class AiToolEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "idempotentHint":
target.getConfiguration().setIdempotentHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
case "openworldhint":
case "openWorldHint":
target.getConfiguration().setOpenWorldHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
+ case "outputparameters":
+ case "outputParameters":
target.getConfiguration().setOutputParameters(property(camelContext,
java.util.Map.class, value)); return true;
+ case "outputschema":
+ case "outputSchema":
target.getConfiguration().setOutputSchema(property(camelContext,
java.lang.String.class, value)); return true;
case "parameters":
target.getConfiguration().setParameters(property(camelContext,
java.util.Map.class, value)); return true;
case "readonlyhint":
case "readOnlyHint":
target.getConfiguration().setReadOnlyHint(property(camelContext,
java.lang.Boolean.class, value)); return true;
@@ -65,6 +69,10 @@ public class AiToolEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "idempotentHint": return java.lang.Boolean.class;
case "openworldhint":
case "openWorldHint": return java.lang.Boolean.class;
+ case "outputparameters":
+ case "outputParameters": return java.util.Map.class;
+ case "outputschema":
+ case "outputSchema": return java.lang.String.class;
case "parameters": return java.util.Map.class;
case "readonlyhint":
case "readOnlyHint": return java.lang.Boolean.class;
@@ -93,6 +101,10 @@ public class AiToolEndpointConfigurer extends
PropertyConfigurerSupport implemen
case "idempotentHint": return
target.getConfiguration().getIdempotentHint();
case "openworldhint":
case "openWorldHint": return
target.getConfiguration().getOpenWorldHint();
+ case "outputparameters":
+ case "outputParameters": return
target.getConfiguration().getOutputParameters();
+ case "outputschema":
+ case "outputSchema": return
target.getConfiguration().getOutputSchema();
case "parameters": return target.getConfiguration().getParameters();
case "readonlyhint":
case "readOnlyHint": return
target.getConfiguration().getReadOnlyHint();
@@ -105,6 +117,8 @@ public class AiToolEndpointConfigurer extends
PropertyConfigurerSupport implemen
@Override
public Object getCollectionValueType(Object target, String name, boolean
ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
+ case "outputparameters":
+ case "outputParameters": return java.lang.String.class;
case "parameters": return java.lang.String.class;
default: return null;
}
diff --git
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointUriFactory.java
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointUriFactory.java
index 0a4c6a4f9e2c..0b573f3a064a 100644
---
a/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointUriFactory.java
+++
b/components/camel-ai/camel-ai-tool/src/generated/java/org/apache/camel/component/ai/tool/AiToolEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class AiToolEndpointUriFactory extends
org.apache.camel.support.component
private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(13);
+ Set<String> props = new HashSet<>(15);
props.add("argSchema");
props.add("bridgeErrorHandler");
props.add("description");
@@ -33,6 +33,8 @@ public class AiToolEndpointUriFactory extends
org.apache.camel.support.component
props.add("exchangePattern");
props.add("idempotentHint");
props.add("openWorldHint");
+ props.add("outputParameters");
+ props.add("outputSchema");
props.add("parameters");
props.add("readOnlyHint");
props.add("tags");
@@ -41,7 +43,8 @@ public class AiToolEndpointUriFactory extends
org.apache.camel.support.component
PROPERTY_NAMES = Collections.unmodifiableSet(props);
SECRET_PROPERTY_NAMES = Collections.emptySet();
ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet();
- Map<String, String> prefixes = new HashMap<>(1);
+ Map<String, String> prefixes = new HashMap<>(2);
+ prefixes.put("outputParameters", "outputParameter.");
prefixes.put("parameters", "parameter.");
MULTI_VALUE_PREFIXES = Collections.unmodifiableMap(prefixes);
}
diff --git
a/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
b/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
index e758755891cc..778564ec6a39 100644
---
a/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
+++
b/components/camel-ai/camel-ai-tool/src/generated/resources/META-INF/org/apache/camel/component/ai/tool/ai-tool.json
@@ -31,11 +31,13 @@
"destructiveHint": { "index": 4, "kind": "property", "displayName":
"Destructive Hint", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool may perform destructive
o [...]
"idempotentHint": { "index": 5, "kind": "property", "displayName":
"Idempotent Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that repeating the tool call with the
sam [...]
"openWorldHint": { "index": 6, "kind": "property", "displayName": "Open
World Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool interacts with external
syst [...]
- "parameters": { "index": 7, "kind": "property", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool in [...]
- "readOnlyHint": { "index": 8, "kind": "property", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not m [...]
- "tags": { "index": 9, "kind": "property", "displayName": "Tags", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags to [...]
- "title": { "index": 10, "kind": "property", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
- "autowiredEnabled": { "index": 11, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
+ "outputParameters": { "index": 7, "kind": "property", "displayName":
"Output Parameters", "group": "consumer", "label": "consumer", "required":
false, "type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "outputParameter.", "multiValue": true,
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "des [...]
+ "outputSchema": { "index": 8, "kind": "property", "displayName": "Output
Schema", "group": "consumer", "label": "consumer", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"supportFileReference": true, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Raw JSON Schema describing the tool's
structured [...]
+ "parameters": { "index": 9, "kind": "property", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool in [...]
+ "readOnlyHint": { "index": 10, "kind": "property", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not [...]
+ "tags": { "index": 11, "kind": "property", "displayName": "Tags", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags t [...]
+ "title": { "index": 12, "kind": "property", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
+ "autowiredEnabled": { "index": 13, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
},
"properties": {
"toolName": { "index": 0, "kind": "path", "displayName": "Tool Name",
"group": "consumer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The tool name. This is the
name the LLM sees and uses to invoke the tool." },
@@ -44,12 +46,14 @@
"destructiveHint": { "index": 3, "kind": "parameter", "displayName":
"Destructive Hint", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool may perform destructive
[...]
"idempotentHint": { "index": 4, "kind": "parameter", "displayName":
"Idempotent Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that repeating the tool call with the
sa [...]
"openWorldHint": { "index": 5, "kind": "parameter", "displayName": "Open
World Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool interacts with external
sys [...]
- "parameters": { "index": 6, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool i [...]
- "readOnlyHint": { "index": 7, "kind": "parameter", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not [...]
- "tags": { "index": 8, "kind": "parameter", "displayName": "Tags", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags t [...]
- "title": { "index": 9, "kind": "parameter", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
- "bridgeErrorHandler": { "index": 10, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
- "exceptionHandler": { "index": 11, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By de [...]
- "exchangePattern": { "index": 12, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." }
+ "outputParameters": { "index": 6, "kind": "parameter", "displayName":
"Output Parameters", "group": "consumer", "label": "consumer", "required":
false, "type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "outputParameter.", "multiValue": true,
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "de [...]
+ "outputSchema": { "index": 7, "kind": "parameter", "displayName": "Output
Schema", "group": "consumer", "label": "consumer", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false,
"supportFileReference": true, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Raw JSON Schema describing the tool's
structured [...]
+ "parameters": { "index": 8, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"configurationClass": "org.apache.camel.component.ai.tool.AiToolConfiguration",
"configurationField": "configuration", "description": "Tool i [...]
+ "readOnlyHint": { "index": 9, "kind": "parameter", "displayName": "Read
Only Hint", "group": "consumer", "label": "consumer", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "defaultValue":
false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "MCP hint that the tool only reads data and
does not [...]
+ "tags": { "index": 10, "kind": "parameter", "displayName": "Tags",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Comma-separated list of tags used to group
tools. Producers filter the registry by these tags [...]
+ "title": { "index": 11, "kind": "parameter", "displayName": "Title",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.ai.tool.AiToolConfiguration", "configurationField":
"configuration", "description": "Optional display title for MCP tool listings.
Advisory hint for MCP clients only." },
+ "bridgeErrorHandler": { "index": 12, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
+ "exceptionHandler": { "index": 13, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By de [...]
+ "exchangePattern": { "index": 14, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." }
}
}
diff --git
a/components/camel-ai/camel-ai-tool/src/main/docs/ai-tool-component.adoc
b/components/camel-ai/camel-ai-tool/src/main/docs/ai-tool-component.adoc
index defb3f0a929c..c13661511ada 100644
--- a/components/camel-ai/camel-ai-tool/src/main/docs/ai-tool-component.adoc
+++ b/components/camel-ai/camel-ai-tool/src/main/docs/ai-tool-component.adoc
@@ -197,6 +197,50 @@ Top-level schema properties are exposed as exchange
headers. Nested values are p
The root schema must be a JSON Schema object with a top-level `properties`
map. Camel always allowlists top-level property names when invoking the tool
route, even if the schema sets `additionalProperties: true`. Nested fields are
not flattened into headers; only top-level properties become exchange headers
(Map/List/primitive values).
+=== Structured Tool Output (outputSchema)
+
+MCP tools can declare an `outputSchema` and return `structuredContent` (typed
JSON) so clients parse tool results reliably instead of re-interpreting free
text. Use `outputParameter.*` for flat field definitions or `outputSchema` for
raw JSON Schema (mirroring the input-side `parameter.*` / `argSchema` pattern).
The two options are mutually exclusive.
+
+When an output schema is declared, the route body must be JSON (a JSON string,
`Map`, or `List`). Camel parses it into structured content and forwards it
through the MCP bridge as `CallToolResult.structuredContent`. The text
representation remains available for LLM adapters (LangChain4j, Spring AI) that
consume string tool results.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("ai-tool:getWeather?tags=weather&description=Get weather"
+ + "&outputSchema=classpath:schemas/weather-result.json")
+ .process(exchange -> exchange.getMessage().setBody(
+ "{\"temperature\":21.5,\"unit\":\"celsius\"}"));
+----
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: ai-tool:getWeather
+ parameters:
+ tags: weather
+ description: "Get weather"
+ outputParameter.temperature: number
+ outputParameter.unit: string
+ steps:
+ - setBody:
+ constant: '{"temperature":21.5,"unit":"celsius"}'
+----
+====
+
+Flat `outputParameter.*` options use the same sub-option syntax as input
`parameter.*`:
+
+* `outputParameter.NAME=TYPE` — field type (`string`, `integer`, `number`,
`boolean`)
+* `outputParameter.NAME.description=TEXT` — field description in the generated
JSON Schema
+* `outputParameter.NAME.required=true` — marks the field as required in the
output schema
+* `outputParameter.NAME.enum=val1,val2` — restricts allowed values
+
+`outputSchema` supports Camel resource references such as
`classpath:schemas/weather-result.json`.
+
=== MCP Tool Annotation Hints
When exposing `ai-tool` routes through the MCP Server component, you can
declare optional behavioral hints aligned with the MCP specification
(`ToolAnnotations`). MCP clients may use these hints for per-tool policy (for
example auto-approving read-only tools or requiring confirmation before
destructive ones).
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolComponent.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolComponent.java
index da179d5f0341..007bcc115696 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolComponent.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolComponent.java
@@ -70,6 +70,12 @@ public class AiToolComponent extends DefaultComponent {
.collect(Collectors.toMap(Map.Entry::getKey, e ->
e.getValue().toString())));
}
+ Map<String, Object> outputToolParameters =
PropertiesHelper.extractProperties(parameters, "outputParameter.");
+ if (!outputToolParameters.isEmpty()) {
+ config.setOutputParameters(outputToolParameters.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e ->
e.getValue().toString())));
+ }
+
AiToolEndpoint endpoint = new AiToolEndpoint(uri, this, toolName,
config);
setProperties(endpoint, parameters);
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConfiguration.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConfiguration.java
index 715ef848e10a..6fcfbb326e0e 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConfiguration.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConfiguration.java
@@ -61,6 +61,21 @@ public class AiToolConfiguration implements Cloneable {
@Metadata(label = "consumer", supportFileReference = true, largeInput =
true, inputLanguage = "json")
private String argSchema;
+ @Metadata(label = "consumer")
+ @UriParam(description = "Tool output schema fields. "
+ + "Format: outputParameter.NAME=TYPE,
outputParameter.NAME.description=TEXT. "
+ + "Supported types: string, integer, number,
boolean. "
+ + "Mutually exclusive with outputSchema.",
+ prefix = "outputParameter.", multiValue = true)
+ private Map<String, String> outputParameters;
+
+ @UriParam(description = "Raw JSON Schema describing the tool's structured
output. Supports inline JSON and "
+ + "resource references (classpath:, file:,
resource:). Mutually exclusive with the "
+ + "outputParameter multi-value options. When
declared, the route body is parsed as JSON "
+ + "and exposed as structured content to MCP
clients.")
+ @Metadata(label = "consumer", supportFileReference = true, largeInput =
true, inputLanguage = "json")
+ private String outputSchema;
+
@Metadata(label = "consumer")
@UriParam(description = "Optional display title for MCP tool listings.
Advisory hint for MCP clients only.")
private String title;
@@ -120,6 +135,22 @@ public class AiToolConfiguration implements Cloneable {
this.argSchema = argSchema;
}
+ public Map<String, String> getOutputParameters() {
+ return outputParameters;
+ }
+
+ public void setOutputParameters(Map<String, String> outputParameters) {
+ this.outputParameters = outputParameters;
+ }
+
+ public String getOutputSchema() {
+ return outputSchema;
+ }
+
+ public void setOutputSchema(String outputSchema) {
+ this.outputSchema = outputSchema;
+ }
+
public String getTitle() {
return title;
}
@@ -166,6 +197,9 @@ public class AiToolConfiguration implements Cloneable {
if (this.parameters != null) {
copy.parameters = new HashMap<>(this.parameters);
}
+ if (this.outputParameters != null) {
+ copy.outputParameters = new HashMap<>(this.outputParameters);
+ }
return copy;
} catch (CloneNotSupportedException e) {
throw new RuntimeCamelException(e);
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConsumer.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConsumer.java
index 1f9c587bb7f4..9309dfe6241a 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConsumer.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolConsumer.java
@@ -52,8 +52,14 @@ public class AiToolConsumer extends DefaultConsumer {
String argSchema = configuration.getArgSchema();
AiToolParameterHelper.validateParameterSourceExclusive(params,
argSchema);
+ Map<String, String> outputParams = configuration.getOutputParameters();
+ String outputSchema = configuration.getOutputSchema();
+ AiToolParameterHelper.validateOutputSourceExclusive(outputParams,
outputSchema);
+
Map<String, AiToolParameterHelper.ParameterDef> parameterDefs =
Map.of();
String jsonSchema = null;
+ Map<String, AiToolParameterHelper.ParameterDef> outputParameterDefs =
Map.of();
+ String outputJsonSchema = null;
if (params != null && !params.isEmpty()) {
parameterDefs =
AiToolParameterHelper.parseParameterMetadata(params);
@@ -62,6 +68,13 @@ public class AiToolConsumer extends DefaultConsumer {
jsonSchema =
AiToolParameterHelper.resolveArgSchema(getEndpoint().getCamelContext(),
argSchema);
}
+ if (outputParams != null && !outputParams.isEmpty()) {
+ outputParameterDefs =
AiToolParameterHelper.parseParameterMetadata(outputParams);
+ outputJsonSchema =
AiToolParameterHelper.buildJsonSchemaFromDefs(outputParameterDefs);
+ } else if (outputSchema != null && !outputSchema.isBlank()) {
+ outputJsonSchema =
AiToolParameterHelper.resolveOutputSchema(getEndpoint().getCamelContext(),
outputSchema);
+ }
+
String desc = configuration.getDescription();
if (desc == null || desc.isBlank()) {
desc = toolName;
@@ -69,7 +82,7 @@ public class AiToolConsumer extends DefaultConsumer {
AiToolAnnotations annotations =
AiToolAnnotations.fromConfiguration(configuration);
registeredSpec = new AiToolSpec(
- toolName, desc, parameterDefs, jsonSchema, annotations, this);
+ toolName, desc, parameterDefs, jsonSchema,
outputParameterDefs, outputJsonSchema, annotations, this);
String tags = configuration.getTags();
String[] parsedTags = (tags != null && !tags.isBlank())
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
index 8f918232d77a..850af4aebc4a 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
@@ -151,11 +151,28 @@ public final class AiToolExecutor {
String result = exchange.getMessage().getBody(String.class);
LOG.debug("Tool '{}' execution completed successfully", toolName);
- return new AiToolResult.Success(result != null ? result : "No
result");
+ return buildSuccessResult(spec, exchange, result);
} catch (Exception e) {
LOG.error("Error executing tool '{}': {}", toolName,
e.getMessage(), e);
return new AiToolResult.ExecutionError(
String.format("Error executing tool '%s': %s", toolName,
e.getMessage()), e);
}
}
+
+ private static AiToolResult buildSuccessResult(AiToolSpec spec, Exchange
exchange, String stringBody) {
+ String outputSchema = spec.getOutputJsonSchema();
+ if (outputSchema == null || outputSchema.isBlank()) {
+ return new AiToolResult.Success(stringBody != null ? stringBody :
"No result");
+ }
+
+ Object body = exchange.getMessage().getBody();
+ try {
+ Object structured =
AiToolParameterHelper.parseStructuredOutput(body);
+ String text =
AiToolParameterHelper.structuredContentToText(structured, body);
+ return new AiToolResult.Success(text, structured);
+ } catch (IllegalArgumentException e) {
+ return new AiToolResult.ExecutionError(
+ String.format("Error executing tool '%s': %s",
spec.getName(), e.getMessage()), e);
+ }
+ }
}
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolParameterHelper.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolParameterHelper.java
index 2be215949b48..f80b906305e3 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolParameterHelper.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolParameterHelper.java
@@ -171,6 +171,18 @@ public final class AiToolParameterHelper {
}
}
+ /**
+ * Validates that flat {@code outputParameter.*} metadata and {@code
outputSchema} are not both configured.
+ */
+ public static void validateOutputSourceExclusive(Map<String, String>
outputParameters, String outputSchema) {
+ boolean hasOutputParameters = outputParameters != null &&
!outputParameters.isEmpty();
+ boolean hasOutputSchema = outputSchema != null &&
!outputSchema.isBlank();
+ if (hasOutputParameters && hasOutputSchema) {
+ throw new IllegalArgumentException(
+ "outputSchema and outputParameter.* are mutually exclusive
on ai-tool endpoints");
+ }
+ }
+
/**
* Resolves, validates, and normalizes a raw JSON Schema for tool input.
*/
@@ -190,6 +202,73 @@ public final class AiToolParameterHelper {
return root.toJson();
}
+ /**
+ * Resolves and normalizes a raw JSON Schema describing tool output. Unlike
+ * {@link #resolveArgSchema(CamelContext, String)}, the schema may
describe any JSON type (object, array, string,
+ * etc.).
+ */
+ public static String resolveOutputSchema(CamelContext camelContext, String
outputSchema) {
+ if (outputSchema == null || outputSchema.isBlank()) {
+ throw new IllegalArgumentException("outputSchema must not be
blank");
+ }
+
+ String resolved =
camelContext.resolvePropertyPlaceholders(outputSchema);
+ String content = resolveResourceContent(camelContext, resolved,
"outputSchema");
+ if (content != null) {
+ resolved = content;
+ }
+
+ JsonObject root = parseJsonObject(resolved, outputSchema,
"outputSchema");
+ return root.toJson();
+ }
+
+ /**
+ * Parses a route body into structured JSON content when an output schema
is declared.
+ */
+ public static Object parseStructuredOutput(Object body) {
+ if (body == null) {
+ throw new IllegalArgumentException(
+ "Route body must not be null when an output schema is
declared");
+ }
+ if (body instanceof Map<?, ?> || body instanceof List<?> || body
instanceof Number || body instanceof Boolean) {
+ return body;
+ }
+ if (body instanceof String text) {
+ if (text.isBlank()) {
+ throw new IllegalArgumentException(
+ "Route body must not be blank when an output schema is
declared");
+ }
+ try {
+ return Jsoner.deserialize(text);
+ } catch (DeserializationException e) {
+ throw new IllegalArgumentException(
+ "Route body must be valid JSON when an output schema
is declared", e);
+ }
+ }
+ throw new IllegalArgumentException(
+ "Route body must be JSON (String, Map, or List) when an output
schema is declared, but was: "
+ + body.getClass().getSimpleName());
+ }
+
+ /**
+ * Serializes structured JSON content to a text representation for LLM
adapters.
+ */
+ public static String structuredContentToText(Object structuredContent,
Object originalBody) {
+ if (structuredContent == null) {
+ return "No result";
+ }
+ if (originalBody instanceof String text && !text.isBlank()) {
+ return text.trim();
+ }
+ if (structuredContent instanceof JsonObject jsonObject) {
+ return jsonObject.toJson();
+ }
+ if (structuredContent instanceof JsonArray jsonArray) {
+ return jsonArray.toJson();
+ }
+ return Jsoner.serialize(structuredContent);
+ }
+
/**
* Returns top-level property names declared in a JSON Schema object.
*/
@@ -227,19 +306,23 @@ public final class AiToolParameterHelper {
}
private static JsonObject parseJsonObject(String json, String
originalValue) {
+ return parseJsonObject(json, originalValue, "argSchema");
+ }
+
+ private static JsonObject parseJsonObject(String json, String
originalValue, String context) {
try {
Object parsed = Jsoner.deserialize(json);
if (parsed == null) {
- throw new IllegalArgumentException("argSchema must be a JSON
object, but was: null");
+ throw new IllegalArgumentException(context + " must be a JSON
object, but was: null");
}
if (!(parsed instanceof JsonObject root)) {
throw new IllegalArgumentException(
- "argSchema must be a JSON object, but was: " +
parsed.getClass().getSimpleName());
+ context + " must be a JSON object, but was: " +
parsed.getClass().getSimpleName());
}
return root;
} catch (DeserializationException e) {
throw new IllegalArgumentException(
- "argSchema does not contain valid JSON. Provided value: "
+ originalValue, e);
+ context + " does not contain valid JSON. Provided value: "
+ originalValue, e);
}
}
@@ -295,6 +378,10 @@ public final class AiToolParameterHelper {
}
private static String resolveResourceContent(CamelContext camelContext,
String property) {
+ return resolveResourceContent(camelContext, property, "argSchema");
+ }
+
+ private static String resolveResourceContent(CamelContext camelContext,
String property, String context) {
try {
if (ResourceHelper.hasScheme(property)) {
try (InputStream is =
ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, property)) {
@@ -307,7 +394,7 @@ public final class AiToolParameterHelper {
}
}
} catch (IOException e) {
- throw new IllegalArgumentException("Failed to load argSchema
resource: " + property, e);
+ throw new IllegalArgumentException("Failed to load " + context + "
resource: " + property, e);
} catch (Exception e) {
// not a resolvable resource URI — fall through and treat as
inline JSON content
}
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolResult.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolResult.java
index 0eb750db9dfb..0345db189efb 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolResult.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolResult.java
@@ -34,9 +34,14 @@ public sealed interface AiToolResult {
/**
* The route executed successfully and produced a result.
*
- * @param value the route result as a string
+ * @param value the route result as a string (JSON text when
structured output is configured)
+ * @param structuredContent typed JSON value when an output schema is
declared, otherwise {@code null}
*/
- record Success(String value) implements AiToolResult {
+ record Success(String value, Object structuredContent) implements
AiToolResult {
+
+ public Success(String value) {
+ this(value, null);
+ }
}
/**
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolSpec.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolSpec.java
index 3a21fb3bbe4a..2ee712b52c62 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolSpec.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolSpec.java
@@ -38,6 +38,8 @@ public final class AiToolSpec {
private final String description;
private final Map<String, AiToolParameterHelper.ParameterDef>
parameterDefs;
private final String parametersJsonSchema;
+ private final Map<String, AiToolParameterHelper.ParameterDef>
outputParameterDefs;
+ private final String outputJsonSchema;
private final Set<String> declaredArgumentNames;
private final Set<String> requiredArgumentNames;
private final AiToolAnnotations annotations;
@@ -48,12 +50,16 @@ public final class AiToolSpec {
String description,
Map<String, AiToolParameterHelper.ParameterDef>
parameterDefs,
String parametersJsonSchema,
+ Map<String, AiToolParameterHelper.ParameterDef>
outputParameterDefs,
+ String outputJsonSchema,
AiToolAnnotations annotations,
DefaultConsumer consumer) {
this.name = name;
this.description = description;
this.parameterDefs = parameterDefs != null ?
Collections.unmodifiableMap(parameterDefs) : Map.of();
this.parametersJsonSchema = parametersJsonSchema;
+ this.outputParameterDefs = outputParameterDefs != null ?
Collections.unmodifiableMap(outputParameterDefs) : Map.of();
+ this.outputJsonSchema = outputJsonSchema;
if (!this.parameterDefs.isEmpty()) {
this.declaredArgumentNames =
Set.copyOf(this.parameterDefs.keySet());
this.requiredArgumentNames = this.parameterDefs.entrySet().stream()
@@ -95,6 +101,20 @@ public final class AiToolSpec {
return parametersJsonSchema;
}
+ /**
+ * Structured output field definitions declared via flat {@code
outputParameter.*} metadata.
+ */
+ public Map<String, AiToolParameterHelper.ParameterDef>
getOutputParameterDefs() {
+ return outputParameterDefs;
+ }
+
+ /**
+ * Pre-built JSON Schema string describing the tool's structured output,
or {@code null} when none is declared.
+ */
+ public String getOutputJsonSchema() {
+ return outputJsonSchema;
+ }
+
/**
* Top-level argument names accepted by this tool. Derived from flat
parameter definitions or from a raw
* {@code argSchema}'s top-level {@code properties} map.
@@ -140,6 +160,8 @@ public final class AiToolSpec {
&& Objects.equals(description, that.description)
&& Objects.equals(parameterDefs, that.parameterDefs)
&& Objects.equals(parametersJsonSchema,
that.parametersJsonSchema)
+ && Objects.equals(outputParameterDefs,
that.outputParameterDefs)
+ && Objects.equals(outputJsonSchema, that.outputJsonSchema)
&& Objects.equals(declaredArgumentNames,
that.declaredArgumentNames)
&& Objects.equals(requiredArgumentNames,
that.requiredArgumentNames)
&& Objects.equals(annotations, that.annotations)
@@ -148,7 +170,8 @@ public final class AiToolSpec {
@Override
public int hashCode() {
- return Objects.hash(name, description, parameterDefs,
parametersJsonSchema, declaredArgumentNames,
+ return Objects.hash(name, description, parameterDefs,
parametersJsonSchema, outputParameterDefs,
+ outputJsonSchema, declaredArgumentNames,
requiredArgumentNames, annotations, consumer);
}
diff --git
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolEndpointLifecycleTest.java
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolEndpointLifecycleTest.java
index e9b1aa829455..0238416bc352 100644
---
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolEndpointLifecycleTest.java
+++
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolEndpointLifecycleTest.java
@@ -383,6 +383,59 @@ public class AiToolEndpointLifecycleTest extends
CamelTestSupport {
assertThat(annotations.openWorldHint()).isTrue();
}
+ @Test
+ void testOutputSchemaToolRegisteredOnStart() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ public void configure() {
+ from("ai-tool:getWeatherStructured"
+ + "?tags=weather-structured"
+ + "&description=Get weather"
+ +
"&outputSchema=classpath:output-schemas/weather-result.json")
+
.setBody(constant("{\"temperature\":21.5,\"unit\":\"celsius\"}"));
+ }
+ });
+
+ AiToolSpec spec =
AiToolRegistry.getOrCreate(context).getToolsByTag("weather-structured").iterator().next();
+
+ assertThat(spec.getName()).isEqualTo("getWeatherStructured");
+
+ assertThat(spec.getOutputParameterDefs()).isEmpty();
+
assertThat(spec.getOutputJsonSchema()).isNotNull().contains("\"temperature\"");
+ }
+
+ @Test
+ void testOutputParameterToolRegisteredOnStart() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ public void configure() {
+ from("ai-tool:getTemperature"
+ + "?tags=weather-output"
+ + "&description=Get temperature"
+ + "&outputParameter.temperature=number"
+ + "&outputParameter.unit=string")
+
.setBody(constant("{\"temperature\":18,\"unit\":\"celsius\"}"));
+ }
+ });
+
+ AiToolSpec spec =
AiToolRegistry.getOrCreate(context).getToolsByTag("weather-output").iterator().next();
+
+ assertThat(spec.getOutputParameterDefs()).containsKeys("temperature",
"unit");
+
assertThat(spec.getOutputJsonSchema()).contains("\"temperature\"").contains("\"unit\"");
+ }
+
+ @Test
+ void testOutputSchemaAndOutputParametersAreMutuallyExclusive() {
+ assertThatThrownBy(() -> context.addRoutes(new RouteBuilder() {
+ public void configure() {
+ from("ai-tool:invalidOutput"
+ + "?tags=test"
+ + "&outputParameter.temperature=number"
+ + "&outputSchema={\"type\":\"object\"}")
+ .setBody(constant("invalid"));
+ }
+ })).isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("mutually exclusive");
+ }
+
@Test
void testGetAllToolsReturnsAllPools() throws Exception {
context.addRoutes(new RouteBuilder() {
diff --git
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
index 028be078bd31..34c71452100f 100644
---
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
+++
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
@@ -85,6 +85,32 @@ public class AiToolExecutorTest extends CamelTestSupport {
List<?> items =
exchange.getMessage().getHeader("items", List.class);
exchange.getMessage().setBody("order:" +
customer.get("id") + ":" + items.size());
});
+
+ from("ai-tool:getWeather"
+ + "?tags=test"
+ + "&description=Get weather"
+ +
"&outputSchema=classpath:output-schemas/weather-result.json")
+
.setBody(constant("{\"temperature\":21.5,\"unit\":\"celsius\"}"));
+
+ from("ai-tool:getWeatherMap"
+ + "?tags=test"
+ + "&description=Get weather as map"
+ + "&outputParameter.temperature=number"
+ + "&outputParameter.unit=string")
+ .process(exchange -> exchange.getMessage().setBody(
+ Map.of("temperature", 18, "unit", "celsius")));
+
+ from("ai-tool:badStructuredOutput"
+ + "?tags=test"
+ + "&description=Returns invalid JSON"
+ +
"&outputSchema=classpath:output-schemas/weather-result.json")
+ .setBody(constant("not-json"));
+
+ from("ai-tool:nullStructuredOutput"
+ + "?tags=test"
+ + "&description=Returns null body with output schema"
+ +
"&outputSchema=classpath:output-schemas/weather-result.json")
+ .setBody(constant((Object) null));
}
};
}
@@ -217,7 +243,7 @@ public class AiToolExecutorTest extends CamelTestSupport {
@Test
public void testExecuteReturnsExecutionErrorForNullConsumer() {
- AiToolSpec spec = new AiToolSpec("ghostTool", "A tool with no
consumer", Map.of(), null, null, null);
+ AiToolSpec spec = new AiToolSpec("ghostTool", "A tool with no
consumer", Map.of(), null, Map.of(), null, null, null);
Exchange exchange = new DefaultExchange(context);
AiToolResult result = AiToolExecutor.execute(spec, null, exchange);
@@ -235,7 +261,7 @@ public class AiToolExecutorTest extends CamelTestSupport {
DefaultConsumer consumerWithNullProcessor = new
DefaultConsumer(endpoint, null);
AiToolSpec spec = new AiToolSpec(
- "nullProc", "test", Map.of(), null, null,
consumerWithNullProcessor);
+ "nullProc", "test", Map.of(), null, Map.of(), null, null,
consumerWithNullProcessor);
Exchange exchange = new DefaultExchange(context);
AiToolResult result = AiToolExecutor.execute(spec, null, exchange);
@@ -370,7 +396,9 @@ public class AiToolExecutorTest extends CamelTestSupport {
"required": ["country"]
}
""";
- AiToolSpec spec = new AiToolSpec("badRequired", "test", Map.of(),
schema, null, findSpec("greetUser").getConsumer());
+ AiToolSpec spec = new AiToolSpec(
+ "badRequired", "test", Map.of(), schema, Map.of(), null, null,
+ findSpec("greetUser").getConsumer());
Exchange exchange = new DefaultExchange(context);
Map<String, Object> arguments = Map.of("country", "Paris");
@@ -381,6 +409,54 @@ public class AiToolExecutorTest extends CamelTestSupport {
assertThat(((AiToolResult.ArgumentError)
result).message()).contains("Missing required argument 'country'");
}
+ @Test
+ public void testExecuteReturnsStructuredContentFromJsonString() {
+ AiToolSpec spec = findSpec("getWeather");
+ Exchange exchange = new DefaultExchange(context);
+
+ AiToolResult result = AiToolExecutor.execute(spec, Map.of(), exchange);
+
+ assertThat(result).isInstanceOf(AiToolResult.Success.class);
+ AiToolResult.Success success = (AiToolResult.Success) result;
+ assertThat(success.value()).contains("\"temperature\":21.5");
+ assertThat(success.structuredContent()).isNotNull();
+ }
+
+ @Test
+ public void testExecuteReturnsStructuredContentFromMapBody() {
+ AiToolSpec spec = findSpec("getWeatherMap");
+ Exchange exchange = new DefaultExchange(context);
+
+ AiToolResult result = AiToolExecutor.execute(spec, Map.of(), exchange);
+
+ assertThat(result).isInstanceOf(AiToolResult.Success.class);
+ AiToolResult.Success success = (AiToolResult.Success) result;
+ assertThat(success.structuredContent()).isInstanceOf(Map.class);
+ assertThat(success.value()).contains("\"temperature\":18");
+ }
+
+ @Test
+ public void testExecuteReturnsExecutionErrorForInvalidStructuredOutput() {
+ AiToolSpec spec = findSpec("badStructuredOutput");
+ Exchange exchange = new DefaultExchange(context);
+
+ AiToolResult result = AiToolExecutor.execute(spec, Map.of(), exchange);
+
+ assertThat(result).isInstanceOf(AiToolResult.ExecutionError.class);
+ assertThat(((AiToolResult.ExecutionError)
result).message()).contains("valid JSON");
+ }
+
+ @Test
+ public void testExecuteReturnsExecutionErrorForNullBodyWithOutputSchema() {
+ AiToolSpec spec = findSpec("nullStructuredOutput");
+ Exchange exchange = new DefaultExchange(context);
+
+ AiToolResult result = AiToolExecutor.execute(spec, Map.of(), exchange);
+
+ assertThat(result).isInstanceOf(AiToolResult.ExecutionError.class);
+ assertThat(((AiToolResult.ExecutionError)
result).message()).contains("must not be null");
+ }
+
private AiToolSpec findSpec(String toolName) {
return
AiToolRegistry.getOrCreate(context).getToolsByTag("test").stream()
.filter(s -> toolName.equals(s.getName()))
diff --git
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolParameterHelperTest.java
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolParameterHelperTest.java
index 81068d72ba90..aa4c8ddbea79 100644
---
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolParameterHelperTest.java
+++
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolParameterHelperTest.java
@@ -371,4 +371,76 @@ public class AiToolParameterHelperTest {
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("properties must be a JSON object");
}
+
+ @Test
+ void testValidateOutputSourceExclusive() {
+ assertThatThrownBy(() ->
AiToolParameterHelper.validateOutputSourceExclusive(
+ Map.of("temperature", "number"), "{\"type\":\"object\"}"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("mutually exclusive");
+ }
+
+ @Test
+ void testResolveOutputSchemaFromInlineJson() {
+ String schema =
AiToolParameterHelper.resolveOutputSchema(camelContext, """
+ {
+ "type": "object",
+ "properties": {
+ "temperature": { "type": "number" }
+ }
+ }
+ """);
+
+ assertThat(schema).contains("\"temperature\"");
+ }
+
+ @Test
+ void testResolveOutputSchemaAllowsNonObjectRootType() {
+ String schema =
AiToolParameterHelper.resolveOutputSchema(camelContext,
"{\"type\":\"string\"}");
+
+ assertThat(schema).contains("\"type\":\"string\"");
+ }
+
+ @Test
+ void testResolveOutputSchemaFromClasspath() {
+ String schema = AiToolParameterHelper.resolveOutputSchema(camelContext,
+ "classpath:output-schemas/weather-result.json");
+
+ assertThat(schema).contains("\"temperature\"").contains("\"unit\"");
+ }
+
+ @Test
+ void testParseStructuredOutputFromJsonString() {
+ Object structured = AiToolParameterHelper.parseStructuredOutput("""
+ {"temperature":21.5,"unit":"celsius"}
+ """);
+
+ assertThat(structured).isInstanceOf(JsonObject.class);
+ assertThat(((JsonObject)
structured).get("temperature").toString()).isEqualTo("21.5");
+ assertThat(((JsonObject)
structured).getString("unit")).isEqualTo("celsius");
+ }
+
+ @Test
+ void testParseStructuredOutputFromMap() {
+ Map<String, Object> body = Map.of("temperature", 21.5, "unit",
"celsius");
+
+ Object structured = AiToolParameterHelper.parseStructuredOutput(body);
+
+ assertThat(structured).isSameAs(body);
+ }
+
+ @Test
+ void testParseStructuredOutputRejectsInvalidJsonString() {
+ assertThatThrownBy(() ->
AiToolParameterHelper.parseStructuredOutput("{not-json"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("valid JSON");
+ }
+
+ @Test
+ void testStructuredContentToTextUsesOriginalJsonString() {
+ String json = "{\"temperature\":21.5,\"unit\":\"celsius\"}";
+ JsonObject structured = (JsonObject)
AiToolParameterHelper.parseStructuredOutput(json);
+
+ assertThat(AiToolParameterHelper.structuredContentToText(structured,
json)).isEqualTo(json);
+ }
}
diff --git
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryListenerTest.java
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryListenerTest.java
index 5244083f452d..25ea81659250 100644
---
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryListenerTest.java
+++
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryListenerTest.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.ai.tool;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -136,7 +137,7 @@ class AiToolRegistryListenerTest {
}
private static AiToolSpec spec(String name) {
- return new AiToolSpec(name, name + " description", null, null, null,
null);
+ return new AiToolSpec(name, name + " description", null, null,
Map.of(), null, null, null);
}
private record Event(String type, String tag, AiToolSpec spec) {
diff --git
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryTest.java
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryTest.java
index d65a153a5c8f..78fabd961758 100644
---
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryTest.java
+++
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolRegistryTest.java
@@ -43,7 +43,7 @@ public class AiToolRegistryTest {
@Test
public void testPutAndGetTool() {
- AiToolSpec spec = new AiToolSpec("getTool", "A test tool", Map.of(),
null, null, null);
+ AiToolSpec spec = new AiToolSpec("getTool", "A test tool", Map.of(),
null, Map.of(), null, null, null);
registry.put("weather", spec);
@@ -56,7 +56,7 @@ public class AiToolRegistryTest {
@Test
public void testRemoveTool() {
- AiToolSpec spec = new AiToolSpec("getTool", "A test tool", Map.of(),
null, null, null);
+ AiToolSpec spec = new AiToolSpec("getTool", "A test tool", Map.of(),
null, Map.of(), null, null, null);
registry.put("weather", spec);
assertThat(registry.getTools().get("weather"))
@@ -71,8 +71,8 @@ public class AiToolRegistryTest {
@Test
public void testMultipleToolsWithSameTag() {
- AiToolSpec spec1 = new AiToolSpec("tool1", "Tool 1", Map.of(), null,
null, null);
- AiToolSpec spec2 = new AiToolSpec("tool2", "Tool 2", Map.of(), null,
null, null);
+ AiToolSpec spec1 = new AiToolSpec("tool1", "Tool 1", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec spec2 = new AiToolSpec("tool2", "Tool 2", Map.of(), null,
Map.of(), null, null, null);
registry.put("assistant", spec1);
registry.put("assistant", spec2);
@@ -84,8 +84,8 @@ public class AiToolRegistryTest {
@Test
public void testRemoveOneOfMultipleTools() {
- AiToolSpec spec1 = new AiToolSpec("tool1", "Tool 1", Map.of(), null,
null, null);
- AiToolSpec spec2 = new AiToolSpec("tool2", "Tool 2", Map.of(), null,
null, null);
+ AiToolSpec spec1 = new AiToolSpec("tool1", "Tool 1", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec spec2 = new AiToolSpec("tool2", "Tool 2", Map.of(), null,
Map.of(), null, null, null);
registry.put("assistant", spec1);
registry.put("assistant", spec2);
@@ -101,8 +101,8 @@ public class AiToolRegistryTest {
@Test
public void testTagIsolation() {
- AiToolSpec weatherSpec = new AiToolSpec("weather", "Weather",
Map.of(), null, null, null);
- AiToolSpec emailSpec = new AiToolSpec("email", "Email", Map.of(),
null, null, null);
+ AiToolSpec weatherSpec = new AiToolSpec("weather", "Weather",
Map.of(), null, Map.of(), null, null, null);
+ AiToolSpec emailSpec = new AiToolSpec("email", "Email", Map.of(),
null, Map.of(), null, null, null);
registry.put("weather", weatherSpec);
registry.put("email", emailSpec);
@@ -120,7 +120,7 @@ public class AiToolRegistryTest {
@Test
public void testRemoveFromNonExistentTag() {
- AiToolSpec spec = new AiToolSpec("tool", "A tool", Map.of(), null,
null, null);
+ AiToolSpec spec = new AiToolSpec("tool", "A tool", Map.of(), null,
Map.of(), null, null, null);
registry.remove("nonexistent", spec);
assertThat(registry.getTools())
@@ -133,7 +133,7 @@ public class AiToolRegistryTest {
@Test
public void testDefaultPoolPutAndRemove() {
- AiToolSpec spec = new AiToolSpec("defaultTool", "Default", Map.of(),
null, null, null);
+ AiToolSpec spec = new AiToolSpec("defaultTool", "Default", Map.of(),
null, Map.of(), null, null, null);
registry.putDefault(spec);
assertThat(registry.getDefaultTools())
@@ -149,8 +149,8 @@ public class AiToolRegistryTest {
@Test
public void testGetToolsByTagIncludesDefaultPool() {
- AiToolSpec taggedTool = new AiToolSpec("tagged", "Tagged", Map.of(),
null, null, null);
- AiToolSpec defaultTool = new AiToolSpec("default", "Default",
Map.of(), null, null, null);
+ AiToolSpec taggedTool = new AiToolSpec("tagged", "Tagged", Map.of(),
null, Map.of(), null, null, null);
+ AiToolSpec defaultTool = new AiToolSpec("default", "Default",
Map.of(), null, Map.of(), null, null, null);
registry.put("weather", taggedTool);
registry.putDefault(defaultTool);
@@ -164,7 +164,7 @@ public class AiToolRegistryTest {
@Test
public void testGetToolsByTagWithNoMatchReturnsDefaultOnly() {
- AiToolSpec defaultTool = new AiToolSpec("default", "Default",
Map.of(), null, null, null);
+ AiToolSpec defaultTool = new AiToolSpec("default", "Default",
Map.of(), null, Map.of(), null, null, null);
registry.putDefault(defaultTool);
assertThat(registry.getToolsByTag("nonexistent"))
@@ -192,7 +192,7 @@ public class AiToolRegistryTest {
}
for (int i = 0; i < opsPerThread; i++) {
AiToolSpec spec = new AiToolSpec(
- "tool-" + threadId + "-" + i, "desc", Map.of(),
null, null, null);
+ "tool-" + threadId + "-" + i, "desc", Map.of(),
null, Map.of(), null, null, null);
registry.put("concurrent", spec);
registry.getToolsByTag("concurrent");
registry.getAllTools();
@@ -215,8 +215,8 @@ public class AiToolRegistryTest {
@Test
public void testDuplicateToolNameUnderSameTagThrows() {
- AiToolSpec spec1 = new AiToolSpec("sameName", "First", Map.of(), null,
null, null);
- AiToolSpec spec2 = new AiToolSpec("sameName", "Second", Map.of(),
null, null, null);
+ AiToolSpec spec1 = new AiToolSpec("sameName", "First", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec spec2 = new AiToolSpec("sameName", "Second", Map.of(),
null, Map.of(), null, null, null);
registry.put("weather", spec1);
@@ -233,8 +233,8 @@ public class AiToolRegistryTest {
@Test
public void testDuplicateToolNameInDefaultPoolThrows() {
- AiToolSpec spec1 = new AiToolSpec("sameName", "First", Map.of(), null,
null, null);
- AiToolSpec spec2 = new AiToolSpec("sameName", "Second", Map.of(),
null, null, null);
+ AiToolSpec spec1 = new AiToolSpec("sameName", "First", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec spec2 = new AiToolSpec("sameName", "Second", Map.of(),
null, Map.of(), null, null, null);
registry.putDefault(spec1);
@@ -251,8 +251,8 @@ public class AiToolRegistryTest {
@Test
public void testSameToolNameDifferentTagsIsAllowed() {
- AiToolSpec spec1 = new AiToolSpec("sameName", "Weather version",
Map.of(), null, null, null);
- AiToolSpec spec2 = new AiToolSpec("sameName", "Email version",
Map.of(), null, null, null);
+ AiToolSpec spec1 = new AiToolSpec("sameName", "Weather version",
Map.of(), null, Map.of(), null, null, null);
+ AiToolSpec spec2 = new AiToolSpec("sameName", "Email version",
Map.of(), null, Map.of(), null, null, null);
registry.put("weather", spec1);
registry.put("email", spec2);
@@ -263,8 +263,8 @@ public class AiToolRegistryTest {
@Test
public void testReRegisterAfterRemoveIsAllowed() {
- AiToolSpec spec1 = new AiToolSpec("tool", "First", Map.of(), null,
null, null);
- AiToolSpec spec2 = new AiToolSpec("tool", "Second", Map.of(), null,
null, null);
+ AiToolSpec spec1 = new AiToolSpec("tool", "First", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec spec2 = new AiToolSpec("tool", "Second", Map.of(), null,
Map.of(), null, null, null);
registry.put("weather", spec1);
registry.remove("weather", spec1);
@@ -277,9 +277,9 @@ public class AiToolRegistryTest {
@Test
public void testGetAllToolsMergesTaggedAndDefault() {
- AiToolSpec tool1 = new AiToolSpec("tool1", "Tool 1", Map.of(), null,
null, null);
- AiToolSpec tool2 = new AiToolSpec("tool2", "Tool 2", Map.of(), null,
null, null);
- AiToolSpec defaultTool = new AiToolSpec("default", "Default",
Map.of(), null, null, null);
+ AiToolSpec tool1 = new AiToolSpec("tool1", "Tool 1", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec tool2 = new AiToolSpec("tool2", "Tool 2", Map.of(), null,
Map.of(), null, null, null);
+ AiToolSpec defaultTool = new AiToolSpec("default", "Default",
Map.of(), null, Map.of(), null, null, null);
registry.put("weather", tool1);
registry.put("email", tool2);
diff --git
a/components/camel-ai/camel-ai-tool/src/test/resources/output-schemas/weather-result.json
b/components/camel-ai/camel-ai-tool/src/test/resources/output-schemas/weather-result.json
new file mode 100644
index 000000000000..ccc90493aa04
--- /dev/null
+++
b/components/camel-ai/camel-ai-tool/src/test/resources/output-schemas/weather-result.json
@@ -0,0 +1,9 @@
+{
+ "type": "object",
+ "properties": {
+ "temperature": { "type": "number" },
+ "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
+ },
+ "required": ["temperature", "unit"],
+ "additionalProperties": false
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/AiToolSpecToLangChain4jTest.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/AiToolSpecToLangChain4jTest.java
index e8904027d1ac..8a1f382b4f45 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/AiToolSpecToLangChain4jTest.java
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/AiToolSpecToLangChain4jTest.java
@@ -41,7 +41,7 @@ class AiToolSpecToLangChain4jTest {
@Test
void testBasicToolSpecConversion() {
- AiToolSpec spec = new AiToolSpec("myTool", "A test tool", Map.of(),
null, null, null);
+ AiToolSpec spec = new AiToolSpec("myTool", "A test tool", Map.of(),
null, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -56,7 +56,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("city", "string");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("weather", "Get weather", defs, null,
null, null);
+ AiToolSpec spec = new AiToolSpec("weather", "Get weather", defs, null,
Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -71,7 +71,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("count", "integer");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("counter", "Count items", defs, null,
null, null);
+ AiToolSpec spec = new AiToolSpec("counter", "Count items", defs, null,
Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -84,7 +84,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("price", "number");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("pricer", "Get price", defs, null,
null, null);
+ AiToolSpec spec = new AiToolSpec("pricer", "Get price", defs, null,
Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -97,7 +97,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("active", "boolean");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("checker", "Check status", defs,
null, null, null);
+ AiToolSpec spec = new AiToolSpec("checker", "Check status", defs,
null, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -111,7 +111,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("color.enum", "red,green,blue");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("colorPicker", "Pick a color", defs,
null, null, null);
+ AiToolSpec spec = new AiToolSpec("colorPicker", "Pick a color", defs,
null, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -129,7 +129,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("userId.required", "true");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("userLookup", "Look up user", defs,
null, null, null);
+ AiToolSpec spec = new AiToolSpec("userLookup", "Look up user", defs,
null, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -147,7 +147,7 @@ class AiToolSpecToLangChain4jTest {
rawParams.put("active", "boolean");
Map<String, AiToolParameterHelper.ParameterDef> defs =
AiToolParameterHelper.parseParameterMetadata(rawParams);
- AiToolSpec spec = new AiToolSpec("multiTool", "Multi-param tool",
defs, null, null, null);
+ AiToolSpec spec = new AiToolSpec("multiTool", "Multi-param tool",
defs, null, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
@@ -189,7 +189,7 @@ class AiToolSpecToLangChain4jTest {
}
""";
- AiToolSpec spec = new AiToolSpec("createOrder", "Create order",
Map.of(), schema, null, null);
+ AiToolSpec spec = new AiToolSpec("createOrder", "Create order",
Map.of(), schema, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
assertThat(result.parameters()).isNotNull();
@@ -199,7 +199,7 @@ class AiToolSpecToLangChain4jTest {
@Test
void testEmptyParameters() {
- AiToolSpec spec = new AiToolSpec("noParams", "Tool with no params",
Map.of(), null, null, null);
+ AiToolSpec spec = new AiToolSpec("noParams", "Tool with no params",
Map.of(), null, Map.of(), null, null, null);
ToolSpecification result =
AiToolSpecToLangChain4j.toToolSpecification(spec);
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
index 1c478aed0b1c..70caf40f3570 100644
---
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
@@ -255,6 +255,11 @@ public class McpServerBridge extends ServiceSupport
implements CamelContextAware
public AiToolAnnotations annotations() {
return spec.getAnnotations();
}
+
+ @Override
+ public String outputSchemaJson() {
+ return spec.getOutputJsonSchema();
+ }
};
}
@@ -284,7 +289,7 @@ public class McpServerBridge extends ServiceSupport
implements CamelContextAware
return new McpToolCallResult(GENERIC_EXECUTION_ERROR, true);
}
if (result instanceof AiToolResult.Success success) {
- return new McpToolCallResult(success.value(), false);
+ return new McpToolCallResult(success.value(), false,
success.structuredContent());
} else if (result instanceof AiToolResult.ArgumentError error) {
return new McpToolCallResult(error.message(), true);
} else {
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerTool.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerTool.java
index f348c6059d2a..3ba36396f5ab 100644
---
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerTool.java
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerTool.java
@@ -60,4 +60,13 @@ public interface McpServerTool {
default AiToolAnnotations annotations() {
return null;
}
+
+ /**
+ * The tool output as a JSON Schema object string, or {@code null} when
the tool declares no structured output.
+ *
+ * @since 4.22
+ */
+ default String outputSchemaJson() {
+ return null;
+ }
}
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpToolCallResult.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpToolCallResult.java
index 526236d5a11b..a57bead1e003 100644
---
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpToolCallResult.java
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpToolCallResult.java
@@ -20,10 +20,15 @@ package org.apache.camel.component.mcp.server;
* Result of an MCP tool invocation, pre-sanitized by the bridge: the text is
safe to return to a remote MCP client and
* never contains raw route exception messages.
*
- * @param text the tool output, or a safe error message when {@code
isError} is true
- * @param isError whether the invocation failed
+ * @param text the tool output, or a safe error message when
{@code isError} is true
+ * @param isError whether the invocation failed
+ * @param structuredContent typed JSON output when the tool declares an output
schema, otherwise {@code null}
*
- * @since 4.22
+ * @since 4.22
*/
-public record McpToolCallResult(String text, boolean isError) {
+public record McpToolCallResult(String text, boolean isError, Object
structuredContent) {
+
+ public McpToolCallResult(String text, boolean isError) {
+ this(text, isError, null);
+ }
}
diff --git
a/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
index 6d9db5c79793..26c927ba5f36 100644
---
a/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
+++
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
@@ -78,6 +78,11 @@ class McpServerBridgeTest extends CamelTestSupport {
from("ai-tool:other_tool?tags=untrusted&description=Other tag")
.setBody(constant("other"));
+
+ from("ai-tool:getWeather?tags=crm&description=Get weather"
+ +
"&outputSchema={\"type\":\"object\",\"properties\":{\"temperature\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"}},\"required\":[\"temperature\",\"unit\"]}")
+ .routeId("weather-route")
+
.setBody(constant("{\"temperature\":21.5,\"unit\":\"celsius\"}"));
}
};
}
@@ -162,6 +167,22 @@ class McpServerBridgeTest extends CamelTestSupport {
assertThat(engine.removed()).containsOnlyOnce("send_email");
}
+ @Test
+ void testToolOutputSchemaPassedThroughBridge() {
+ McpServerTool tool = engine.tools().get("getWeather");
+
+
assertThat(tool.outputSchemaJson()).isNotNull().contains("\"temperature\"");
+ }
+
+ @Test
+ void testCallToolReturnsStructuredContent() {
+ McpToolCallResult result =
engine.tools().get("getWeather").handler().call(Map.of());
+
+ assertThat(result.isError()).isFalse();
+ assertThat(result.text()).contains("\"temperature\":21.5");
+ assertThat(result.structuredContent()).isNotNull();
+ }
+
@Test
void testNameCollisionIsRefused() throws Exception {
McpServerTool published = engine.tools().get("query_db");
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
index a0435525722c..63ed18dfc355 100644
---
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
@@ -149,10 +149,13 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
.callHandler((exchange, request) -> {
Map<String, Object> arguments = request.arguments() !=
null ? request.arguments() : Map.of();
McpToolCallResult result = tool.handler().call(arguments);
- return McpSchema.CallToolResult.builder()
+ McpSchema.CallToolResult.Builder builder =
McpSchema.CallToolResult.builder()
.addTextContent(result.text())
- .isError(result.isError())
- .build();
+ .isError(result.isError());
+ if (result.structuredContent() != null) {
+ builder.structuredContent(result.structuredContent());
+ }
+ return builder.build();
})
.build();
server.addTool(spec);
@@ -177,6 +180,9 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
String schema = tool.inputSchemaJson() != null ?
tool.inputSchemaJson() : EMPTY_OBJECT_SCHEMA;
McpSchema.Tool.Builder builder = McpSchema.Tool.builder(tool.name(),
jsonMapper, schema)
.description(tool.description());
+ if (tool.outputSchemaJson() != null &&
!tool.outputSchemaJson().isBlank()) {
+ builder.outputSchema(jsonMapper, tool.outputSchemaJson());
+ }
applyAnnotations(builder, tool.annotations());
return builder.build();
}
diff --git
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerOutputSchemaTest.java
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerOutputSchemaTest.java
new file mode 100644
index 000000000000..95553c81d9a7
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerOutputSchemaTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.mcp.server.vertx;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import
io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.mcp.server.McpServerInfo;
+import org.apache.camel.component.mcp.server.McpServerTool;
+import org.apache.camel.component.mcp.server.McpToolCallHandler;
+import org.apache.camel.component.mcp.server.McpToolCallResult;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class VertxMcpServerOutputSchemaTest {
+
+ private static final String OUTPUT_SCHEMA = """
+ {
+ "type": "object",
+ "properties": {
+ "temperature": { "type": "number" },
+ "unit": { "type": "string" }
+ },
+ "required": ["temperature", "unit"]
+ }
+ """;
+
+ @Test
+ void testEnginePublishesOutputSchemaAndStructuredContent() throws
Exception {
+ int port = AvailablePortFinder.getNextAvailable();
+ CamelContext camelContext = new DefaultCamelContext();
+ VertxMcpServerEngine engine = new VertxMcpServerEngine();
+ McpSyncClient client = null;
+ try {
+ MainHttpServer main = new MainHttpServer();
+ main.setCamelContext(camelContext);
+ main.setHost("0.0.0.0");
+ main.setPort(port);
+ camelContext.addService(main);
+ camelContext.start();
+
+ engine.setCamelContext(camelContext);
+ engine.initialize(new McpServerInfo("output-schema", "1.0",
"/mcp"));
+ engine.start();
+
+ JsonObject structured = new JsonObject();
+ structured.put("temperature", 21.5);
+ structured.put("unit", "celsius");
+ engine.toolAdded(tool("getWeather", "Get weather", OUTPUT_SCHEMA,
+ arguments -> new McpToolCallResult(structured.toJson(),
false, structured)));
+
+ client =
McpClient.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" +
port).build())
+ .requestTimeout(Duration.ofSeconds(10))
+ .initializationTimeout(Duration.ofSeconds(10))
+ .build();
+ client.initialize();
+
+ List<McpSchema.Tool> tools = client.listTools().tools();
+ McpSchema.Tool weather = tools.stream().filter(t ->
"getWeather".equals(t.name())).findFirst().orElseThrow();
+
+ assertThat(weather.outputSchema()).isNotNull();
+ assertThat(weather.outputSchema()).containsKey("properties");
+
+ McpSchema.CallToolResult result = client.callTool(new
McpSchema.CallToolRequest("getWeather", Map.of()));
+
+ assertThat(result.isError()).isFalse();
+ assertThat(result.structuredContent()).isNotNull();
+ assertThat(result.structuredContent()).isInstanceOf(Map.class);
+ assertThat(((Map<?, ?>)
result.structuredContent()).get("temperature")).isEqualTo(21.5);
+ } finally {
+ if (client != null) {
+ client.closeGracefully();
+ }
+ engine.stop();
+ camelContext.stop();
+ }
+ }
+
+ private static McpServerTool tool(
+ String name, String description, String outputSchemaJson,
McpToolCallHandler handler) {
+ return new McpServerTool() {
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String description() {
+ return description;
+ }
+
+ @Override
+ public String inputSchemaJson() {
+ return null;
+ }
+
+ @Override
+ public Map<String,
org.apache.camel.component.ai.tool.AiToolParameterHelper.ParameterDef>
parameters() {
+ return Map.of();
+ }
+
+ @Override
+ public McpToolCallHandler handler() {
+ return handler;
+ }
+
+ @Override
+ public String outputSchemaJson() {
+ return outputSchemaJson;
+ }
+ };
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 1c4c03497e19..da5d4261420f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -31,6 +31,18 @@ TTL of 5 minutes (evict sessions with no POST/GET activity).
Configure with
`camel.server.mcp-session-keep-alive-interval` and
`camel.server.mcp-session-idle-ttl`; set either
option to `0` to disable that mechanism.
+=== camel-ai-tool
+
+`AiToolResult.Success` now carries an optional `structuredContent` field when
the tool declares an
+output schema (`outputParameter.*` or `outputSchema`). The MCP bridge maps
this to
+`CallToolResult.structuredContent` and publishes `Tool.outputSchema` to MCP
clients. LangChain4j
+and Spring AI adapters continue to use the string `value()` only.
+
+SPI extensions in this release (before 4.22 ships): `McpToolCallResult` gains
a third
+`structuredContent` component; `McpServerTool` adds `outputSchemaJson()`;
`AiToolSpec` constructor
+accepts output schema fields. Custom `McpServerEngine` implementations should
forward structured
+content when present.
+
=== camel-core
==== Property placeholders in toD and enrich dynamic endpoint URIs
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AiToolComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AiToolComponentBuilderFactory.java
index 21ab68bca404..f74b4a8b9a0d 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AiToolComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AiToolComponentBuilderFactory.java
@@ -189,6 +189,47 @@ public interface AiToolComponentBuilderFactory {
return this;
}
+ /**
+ * Tool output schema fields. Format: outputParameter.NAME=TYPE,
+ * outputParameter.NAME.description=TEXT. Supported types: string,
+ * integer, number, boolean. Mutually exclusive with outputSchema. This
+ * is a multi-value option with prefix: outputParameter.
+ *
+ * The option is a: <code>java.util.Map&lt;java.lang.String,
+ * java.lang.String&gt;</code> type.
+ *
+ * Group: consumer
+ *
+ * @param outputParameters the value to set
+ * @return the dsl builder
+ */
+ default AiToolComponentBuilder
outputParameters(java.util.Map<java.lang.String, java.lang.String>
outputParameters) {
+ doSetProperty("outputParameters", outputParameters);
+ return this;
+ }
+
+ /**
+ * Raw JSON Schema describing the tool's structured output. Supports
+ * inline JSON and resource references (classpath:, file:, resource:).
+ * Mutually exclusive with the outputParameter multi-value options.
When
+ * declared, the route body is parsed as JSON and exposed as structured
+ * content to MCP clients.
+ *
+ * This option can also be loaded from an existing file, by prefixing
+ * with file: or classpath: followed by the location of the file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: consumer
+ *
+ * @param outputSchema the value to set
+ * @return the dsl builder
+ */
+ default AiToolComponentBuilder outputSchema(java.lang.String
outputSchema) {
+ doSetProperty("outputSchema", outputSchema);
+ return this;
+ }
+
/**
* Tool input parameters. Format: parameter.NAME=TYPE,
* parameter.NAME.description=TEXT, parameter.NAME.required=true or
@@ -310,6 +351,8 @@ public interface AiToolComponentBuilderFactory {
case "destructiveHint": getOrCreateConfiguration((AiToolComponent)
component).setDestructiveHint((java.lang.Boolean) value); return true;
case "idempotentHint": getOrCreateConfiguration((AiToolComponent)
component).setIdempotentHint((java.lang.Boolean) value); return true;
case "openWorldHint": getOrCreateConfiguration((AiToolComponent)
component).setOpenWorldHint((java.lang.Boolean) value); return true;
+ case "outputParameters":
getOrCreateConfiguration((AiToolComponent)
component).setOutputParameters((java.util.Map) value); return true;
+ case "outputSchema": getOrCreateConfiguration((AiToolComponent)
component).setOutputSchema((java.lang.String) value); return true;
case "parameters": getOrCreateConfiguration((AiToolComponent)
component).setParameters((java.util.Map) value); return true;
case "readOnlyHint": getOrCreateConfiguration((AiToolComponent)
component).setReadOnlyHint((java.lang.Boolean) value); return true;
case "tags": getOrCreateConfiguration((AiToolComponent)
component).setTags((java.lang.String) value); return true;
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AiToolEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AiToolEndpointBuilderFactory.java
index 74e7abf42bfd..9d3e7cb45aab 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AiToolEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AiToolEndpointBuilderFactory.java
@@ -182,6 +182,70 @@ public interface AiToolEndpointBuilderFactory {
doSetProperty("openWorldHint", openWorldHint);
return this;
}
+ /**
+ * Tool output schema fields. Format: outputParameter.NAME=TYPE,
+ * outputParameter.NAME.description=TEXT. Supported types: string,
+ * integer, number, boolean. Mutually exclusive with outputSchema. This
+ * is a multi-value option with prefix: outputParameter.
+ *
+ * The option is a: <code>java.util.Map<java.lang.String,
+ * java.lang.String></code> type.
+ * The option is multivalued, and you can use the
+ * outputParameters(String, Object) method to add a value (call the
+ * method multiple times to set more values).
+ *
+ * Group: consumer
+ *
+ * @param key the option key
+ * @param value the option value
+ * @return the dsl builder
+ */
+ default AiToolEndpointBuilder outputParameters(String key, Object
value) {
+ doSetMultiValueProperty("outputParameters", "outputParameter." +
key, value);
+ return this;
+ }
+ /**
+ * Tool output schema fields. Format: outputParameter.NAME=TYPE,
+ * outputParameter.NAME.description=TEXT. Supported types: string,
+ * integer, number, boolean. Mutually exclusive with outputSchema. This
+ * is a multi-value option with prefix: outputParameter.
+ *
+ * The option is a: <code>java.util.Map<java.lang.String,
+ * java.lang.String></code> type.
+ * The option is multivalued, and you can use the
+ * outputParameters(String, Object) method to add a value (call the
+ * method multiple times to set more values).
+ *
+ * Group: consumer
+ *
+ * @param values the values
+ * @return the dsl builder
+ */
+ default AiToolEndpointBuilder outputParameters(Map values) {
+ doSetMultiValueProperties("outputParameters", "outputParameter.",
values);
+ return this;
+ }
+ /**
+ * Raw JSON Schema describing the tool's structured output. Supports
+ * inline JSON and resource references (classpath:, file:, resource:).
+ * Mutually exclusive with the outputParameter multi-value options.
When
+ * declared, the route body is parsed as JSON and exposed as structured
+ * content to MCP clients.
+ *
+ * This option can also be loaded from an existing file, by prefixing
+ * with file: or classpath: followed by the location of the file.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: consumer
+ *
+ * @param outputSchema the value to set
+ * @return the dsl builder
+ */
+ default AiToolEndpointBuilder outputSchema(String outputSchema) {
+ doSetProperty("outputSchema", outputSchema);
+ return this;
+ }
/**
* Tool input parameters. Format: parameter.NAME=TYPE,
* parameter.NAME.description=TEXT, parameter.NAME.required=true or