Skip to content

CAMEL-23397: Support route tool annotations (returnDirect) in camel-openai agentic loop - #25497

Open
atiaomar1978-hub wants to merge 4 commits into
apache:mainfrom
atiaomar1978-hub:feature/CAMEL-23397-openai-tool-annotations-c587
Open

CAMEL-23397: Support route tool annotations (returnDirect) in camel-openai agentic loop#25497
atiaomar1978-hub wants to merge 4 commits into
apache:mainfrom
atiaomar1978-hub:feature/CAMEL-23397-openai-tool-annotations-c587

Conversation

@atiaomar1978-hub

Copy link
Copy Markdown
Contributor

Description

AI-generated on behalf of atiaomar1978-hub using Cursor.

Implements CAMEL-23397: parity between MCP tools and route-based ai-tool tools in the camel-openai agentic loop.

Changes

  • camel-ai-tool: add returnDirect to AiToolAnnotations / AiToolConfiguration and document it.
  • camel-mcp-server: publish returnDirect in MCP tool annotations via VertxMcpServerEngine.
  • camel-openai:
    • new tags endpoint option to discover route tools from the shared AiToolRegistry
    • convert route tools to OpenAI function tools (AiToolSpecToOpenAI, OpenAIRouteToolSupport)
    • execute route tools in McpToolCallExecutor via AiToolExecutor
    • honour returnDirect annotations when short-circuiting the agentic loop
    • refresh route tools dynamically when the registry changes
    • support manual tool-execution loops with tags-only route tools

Testing

./mvnw -pl components/camel-ai/camel-ai-tool,components/camel-ai/camel-openai -am test \
  -Dtest=AiToolAnnotationsTest,OpenAIRouteToolSupportTest,OpenAIRouteToolReturnDirectTest,McpToolCallExecutorTest,OpenAIProducerMcpMockTest,OpenAIEndpointMcpToolRefreshTest

Target

  • I checked that the commit is targeting the correct branch (Camel 4 uses the main branch)

Tracking

  • If this is a large change, bug fix, or code improvement, I checked there is a JIRA issue filed for the change (usually before you start working on it).

Apache Camel coding standards and style

  • I checked that each commit in the pull request has a meaningful subject line and body.
  • I have run mvn clean install -DskipTests locally from root folder and I have committed all auto-generated changes.

AI-assisted contributions

  • If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., Co-authored-by trailers) and the PR description identifies the AI tool used.

@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

Bugbot re-review follow-up

AI-generated on behalf of atiaomar1978-hub using Cursor.

After the initial bugbot review, the following issues were addressed in commits 39500554a24b and 799b8ae0dbd8:

Fixed

  1. Route deregistration dropped MCP toolsrepublishCombinedState now keeps MCP-backed tools via toolClientMap instead of filtering by previous route-tool names.
  2. Duplicate tools on name clash — route tools shadow MCP tools with the same name in the advertised tool list (route execution already took precedence).
  3. tool-execution rejected route-only toolsOpenAIToolExecutionProducer now accepts endpoints configured with tags only (no mcpServer.*).
  4. MCP returnDirect lost after shadowing — MCP and route returnDirect flags are merged separately; MCP flags are preserved when a route tool is removed.
  5. MCP refresh bypassed route mergerepublishServerTools now calls republishCombinedState() after MCP reconnect / tools/list_changed refresh so route-tool shadowing and returnDirect merging stay consistent.

Tests re-run

./mvnw -pl components/camel-ai/camel-openai -am test \
  -Dtest=OpenAIRouteToolSupportTest,OpenAIRouteToolReturnDirectTest,McpToolCallExecutorTest,OpenAIEndpointMcpToolRefreshTest

All passed locally.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

…-openai

Add returnDirect to ai-tool annotations/configuration and publish it via
the MCP server bridge. Wire camel-openai to the shared AiToolRegistry
through a tags option so route-based tools are exposed alongside MCP
tools, executed via AiToolExecutor, and honour returnDirect in the
agentic loop.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep MCP tools and returnDirect flags when route tools are refreshed or
removed, dedupe shadowed MCP tools when a route tool shares the same
name, and allow manual tool-execution loops with tags-only route tools.

Co-authored-by: Cursor <cursoragent@cursor.com>
Run republishCombinedState after MCP reconnect and tool-list refresh so
route tool shadowing and returnDirect merging stay consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>
@davsclaus
davsclaus force-pushed the feature/CAMEL-23397-openai-tool-annotations-c587 branch from 799b8ae to 7f4506e Compare August 14, 2026 07:59
@davsclaus

Copy link
Copy Markdown
Contributor

There are uncommitted changes
HEAD detached at pull/25497/merge
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ai-tool.json
modified: catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
modified: catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ai-tool-component.adoc
modified: catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openai-mcp.adoc
modified: dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/AiToolComponentBuilderFactory.java
modified: dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AiToolEndpointBuilderFactory.java
modified: dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java

Commit generated ai-tool and openai catalog, documentation, and DSL
artifacts so CI sourcecheck passes after adding returnDirect and tags
options.

Co-authored-by: Cursor <cursoragent@cursor.com>
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

Fixed CI uncommitted changes failure in run 31782070449.

Regenerated and committed missing catalog/DSL artifacts for returnDirect (ai-tool) and tags (openai):

  • catalog/.../ai-tool.json, openai.json, docs
  • AiToolComponentBuilderFactory, AiToolEndpointBuilderFactory, OpenAIEndpointBuilderFactory

Commit: 42553cbd

AI-generated on behalf of atiaomar1978-hub

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of davsclaus

Thanks for the contribution — the feature design is solid and the test coverage is good. A few issues to address before this can merge.

Findings

1. AiToolAnnotations record constructor is public API — adding a field is a binary-breaking change (High)

AiToolAnnotations is a public record (since 4.22) in camel-ai-tool. Adding returnDirect as a 6th constructor parameter breaks any downstream code constructing new AiToolAnnotations(title, readOnly, destructive, idempotent, openWorld) — that 5-arg call no longer compiles. Per project conventions: "Do NOT change public API signatures without justification." If 4.22 has not shipped yet this is harmless (pre-release), but please confirm and note it in the PR description. If it has shipped, a secondary 5-arg compat constructor is needed.

2. FQCNs used in McpToolState.knownToolNames() (Medium)

The new method uses fully-qualified class names inline (java.util.Set, java.util.HashSet). Per project conventions: "Do NOT use fully qualified class names in Java code. Always add an import statement and use the simple class name." Both Set and HashSet are already imported at the top of the file — just use the simple names.

3. Dead release variable in McpToolCallExecutor.executeRouteTool() (Medium)

boolean release = true; is set once and never changed to false, making the if (release) branch unconditional. Remove the variable and call releaseExchange directly in the finally block.

4. New compile dependency camel-ai-toolcamel-openai — needs justification (Low)

Per project conventions: "Do NOT add new dependencies without justification." This coupling is architecturally reasonable, but please add a brief note in the PR description explaining why camel-openai needs a compile dependency on camel-ai-tool rather than going through an SPI.

5. Commit co-authorship attribution

All 4 commits show cursoragent as the author. Per project AI contribution rules: "commits have proper co-authorship attribution (e.g., Co-authored-by trailers)." The PR description mentions Cursor, but the individual commits should carry the trailer.


Note: CI checks are still pending at time of review. This review does not replace specialized AI review tools (CodeRabbit, Sourcery) or static analyzers (SonarCloud).

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.


static McpToolState empty() {
return new McpToolState(List.of(), Map.of(), Map.of(), Set.of());
return new McpToolState(List.of(), Map.of(), Map.of(), Set.of(), Map.of());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These use fully-qualified class names (java.util.Set, java.util.HashSet) but both are already imported at the top of this file. Per project conventions, use the simple class names:

Suggested change
return new McpToolState(List.of(), Map.of(), Map.of(), Set.of(), Map.of());
Set<String> knownToolNames() {
Set<String> names = new HashSet<>(toolClientMap.keySet());


try {
Map<String, Object> argsMap = OBJECT_MAPPER.readValue(argsJson, Map.class);
Exchange toolExchange = spec.getConsumer().getEndpoint().createExchange();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

release is set to true and never changed — the conditional is always entered. Remove the variable and call releaseExchange unconditionally in the finally block.

Suggested change
Exchange toolExchange = spec.getConsumer().getEndpoint().createExchange();
boolean release = true;

Should become just removing the variable and changing if (release) { to unconditional.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-ai/camel-ai-tool
  • components/camel-ai/camel-mcp-server
  • components/camel-ai/camel-openai
  • dsl/camel-componentdsl
  • dsl/camel-endpointdsl

🔬 Scalpel shadow comparison — Scalpel: 17 tested, 26 compile-only — current: 14 all tested

Maveniverse Scalpel detected 43 affected modules (current approach: 14).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 17 modules (6 direct + 11 downstream), skip tests for 26 (generated code, meta-modules)

Modules Scalpel would test (17)
  • camel-ai-tool
  • camel-catalog
  • camel-componentdsl
  • camel-endpointdsl
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-langchain4j-agent
  • camel-launcher-container
  • camel-mcp-server
  • camel-mcp-server-api
  • camel-openai
  • camel-spring-ai-chat
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (26)
  • apache-camel
  • camel-allcomponents
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-csimple-maven-plugin
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

⚠️ Some tests are disabled on GitHub Actions (@DisabledIfSystemProperty(named = "ci.env.name")) and require manual verification:

  • components/camel-ai/camel-mcp-server: 1 test(s) disabled on GitHub Actions
  • components/camel-ai/camel-openai: 7 test(s) disabled on GitHub Actions
All tested modules (43 modules)
  • Camel :: AI :: LangChain4j :: Agent
  • Camel :: AI :: MCP Server
  • Camel :: AI :: MCP Server API
  • Camel :: AI :: OpenAI
  • Camel :: AI :: Tool
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: Spring AI :: Chat
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants