Skip to content

CAMEL-24311: camel.server.mcp-* configuration properties and camel-main autowiring - #25309

Draft
Croway wants to merge 11 commits into
apache:mainfrom
Croway:CAMEL-24311-mcp-server-properties
Draft

CAMEL-24311: camel.server.mcp-* configuration properties and camel-main autowiring#25309
Croway wants to merge 11 commits into
apache:mainfrom
Croway:CAMEL-24311-mcp-server-properties

Conversation

@Croway

@Croway Croway commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

CAMEL-24311: camel.server.mcp-* configuration properties and camel-main autowiring

Sub-tasks of CAMEL-24308: implements CAMEL-24311 and completes the docs of CAMEL-24314.

Stacked on #25306 (which is stacked on #25301) — review only the CAMEL-24311 commit. Will rebase as the underlying PRs merge.

What this adds

The MCP server now delivers the parent issue's headline UX on Camel Main / JBang — no code and no route for the server itself, like Jolokia or Prometheus:

camel.server.enabled = true
camel.server.mcp-enabled = true
camel.server.mcp-tags = crm,notify
camel.server.mcp-server-name = my-integration-app
  • HttpServerConfigurationProperties: mcpEnabled (false), mcpTags, mcpToolTimeout (20s), mcpPath (/mcp), mcpServerName — with fluent withers; configurer, camel-main-configuration-metadata.json and main.adoc regenerated.
  • McpServerFactory SPI in camel-main, mirroring MainHttpServerFactory: BaseMainSupport resolves it (registry bean → bootstrap FactoryFinder key mcp-server, jar hint camel-mcp-server) when mcp-enabled=true and adds the bridge after the HTTP server service, so the Vert.x engine finds the running router. With camel.server.enabled=false the bridge is still set up so startup fails fast with the engine's actionable message instead of silently doing nothing.
  • DefaultMcpServerFactory (@JdkService) in camel-mcp-server maps the options onto McpServerConfiguration. It lives in the engine module deliberately: that module is main/JBang-only, so camel-main never reaches the Quarkus/Spring Boot classpath through camel-mcp-server-api (precedent: camel-platform-http-main implementing MainHttpServerFactory).
  • Docs (CAMEL-24314): the component page now leads with the properties quick start (the interim "tracked by CAMEL-24311" note is gone); the options table names the camel.server.mcp-* properties per the bridge/engine ownership split.
  • Catalog harvest for the new modules (others.properties, others/mcp-server.json, docs copies) — generated here because the packaging-plugin change from CAMEL-24310: camel-mcp-server - bridge, McpServerEngine SPI and Vert.x engine #25306 needs a rebuilt plugin; can be split into CAMEL-24310: camel-mcp-server - bridge, McpServerEngine SPI and Vert.x engine #25306 if its CI requires it.

Property ownership on native-engine runtimes is unchanged (per CAMEL-24311): quarkus.mcp.server.* / spring.ai.mcp.server.* win for serving concerns; bridge options are honored everywhere.

Testing

McpServerMainPropertiesTest:

  • boots org.apache.camel.main.Main from initial properties only, then verifies with the official MCP SDK client: advertised server name, tag-filtered tools/list (untagged pool excluded), successful tools/call.
  • verifies mcp-enabled=true without the HTTP server fails fast with the actionable message.

Full module suite green (16 unit tests + 13 IT scenarios across the stack).


This PR was written by Claude Code on behalf of Federico Mariani (@Croway).

🤖 Generated with Claude Code

Croway and others added 5 commits August 3, 2026 09:31
…istration changes

Add AiToolRegistryListener with toolRegistered/toolDeregistered callbacks
fired on ai-tool consumer lifecycle events (route start/resume registers,
stop/suspend deregisters). Callbacks fire outside the registry lock, only
on actual state changes, and a failing listener cannot break registration.
Prerequisite for MCP tools/list_changed notifications (CAMEL-24308).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x engine

Expose ai-tool routes (CAMEL-23382) as MCP tools over streamable HTTP:

- camel-mcp-server-api: runtime-agnostic bridge + McpServerEngine SPI.
  The bridge selects tools by tag (untagged default pool never exposed),
  refuses flat-namespace name collisions, executes via AiToolExecutor
  with a bounded per-call timeout, sanitizes execution errors, and
  reacts to AiToolRegistry listener events (CAMEL-24309). Enforcer rule
  bans MCP SDK/Reactor/Vert.x/platform-http from compile/runtime scope.
  Ships the engine conformance kit as a test-jar (CAMEL-24313).
- camel-mcp-server: engine for Camel Main/JBang on the official MCP
  Java SDK with a custom Vert.x streamable HTTP transport registered on
  the platform HTTP router (the SDK ships only servlet/stdio server
  transports): POST json/SSE, GET SSE channel with Last-Event-ID
  replay, Mcp-Session-Id sessions, DELETE termination, tools/
  list_changed on route lifecycle (CAMEL-24312).

The api/engine module naming differs from the sub-task sketch
(engine-as-runtime-dep would be a Maven dependency cycle): plain Camel
users add camel-mcp-server; native-engine runtimes depend on
camel-mcp-server-api, following the camel-langchain4j-agent-api
precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- MainHttpServerMcpConformanceIT: the engine conformance kit against the
  Camel main HTTP server (camel-platform-http-main), the real Camel
  Main/JBang serving path. No Docker required.
- McpServerOpenAIAgentIT: end-to-end agentic loop from CAMEL-24308 - the
  application exposes its own ai-tool routes over MCP and an LLM
  (camel-openai + Ollama test-infra) discovers and calls them with
  automatic tool execution. CI-gated like the other AI component ITs.
- test-execution.md run-book following the camel-openai convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fix engine-resolution error message to hint the real artifact
  (camel-mcp-server, not the pre-rename engine module name)
- Add @SInCE 4.22 to the public SPI types in camel-mcp-server-api
- Drop the session from the provider map on a failed SSE write, like
  the SDK servlet reference transport (session TTL/keep-alive eviction
  stays a follow-up)
- Bound notifyClients/closeGracefully per-session blocks with a 5s
  timeout so one stalled session cannot starve the others
- Reduce MESSAGE_EVENT_TYPE to private

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Croway Croway added the enhancement New feature or request label Aug 3, 2026
@Croway
Croway marked this pull request as draft August 3, 2026 10:10

@gnodet gnodet 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.

🤖 AI-assisted review — Claude Code on behalf of @gnodet


Review: CAMEL-24311 — MCP server configuration properties and camel-main autowiring

Very well-designed PR. The architecture cleanly separates the runtime-agnostic bridge (McpServerBridge in camel-mcp-server-api) from the Vert.x serving engine (VertxMcpStreamableServerEngine in camel-mcp-server), following the same pattern as MainHttpServerFactory / camel-platform-http-main. The security model is sound (tag-based opt-in, error sanitization, name collision protection), and test coverage is comprehensive with unit tests, a shared conformance test kit, and integration tests.

A few observations worth discussing — none are blocking.

Observations

1. [MINOR] Race window between publish/unpublish and engine notification
McpServerBridge.publish() adds to the published map under the lock, then calls engine.toolAdded(tool) outside the lock. Similarly, unpublish removes from the map under the lock, then calls engine.toolRemoved() outside. If unpublish runs between lock release and toolAdded, the tool ends up in the engine with no entry in published — orphaned and unremovable. In practice this is unlikely since route lifecycle events are serialized, but it's worth being aware of for future engines that may not be as forgiving. Moving the engine notification inside the lock (or using a happens-before guarantee) would close the window.

2. [MINOR] Exchange accumulation under sustained timeout pressure
In McpServerBridge.execute(), when a TimeoutException occurs, release is set to false and the exchange is intentionally not returned to the pool (the route may still be using it). This is the correct trade-off to avoid use-after-free, but under sustained timeout pressure, exchanges accumulate with no deferred cleanup. Consider logging the exchange identity in the warning message to aid debugging if operators ever see memory pressure from accumulated exchanges.

3. [MINOR] Missing Content-Type validation on POST requests
VertxMcpStreamableServerTransportProvider.handlePost() validates the Accept header (must include both text/event-stream and application/json) but does not validate the incoming Content-Type. The MCP Streamable HTTP specification requires Content-Type: application/json on POST requests. While malformed JSON bodies would still fail with a 400 during parsing, an explicit content-type check would be more spec-conformant and reject invalid requests earlier with a clearer error message.

Nits

  • VertxMcpStreamableServerTransportProvider.handleInitialize()init.initResult().block() has no timeout. If the MCP SDK's initialization hangs, this blocks the worker thread indefinitely. Consider adding block(Duration.ofSeconds(30)) consistent with the notification timeout pattern used elsewhere.
  • McpServerFactory in core/camel-main — missing @since 4.22 Javadoc tag. While the convention is primarily for core/camel-api, it would be consistent with the other new public types in this PR.
  • test-execution.md — useful content, but an unusual pattern in the Camel codebase. Consider whether this information fits better in the module README or as a comment in the pom.xml.

Overall, excellent work — clean separation of concerns, solid test coverage, and a well-thought-out security model. 👍

Croway and others added 2 commits August 3, 2026 12:26
Use same-version xrefs (ROOT: module / relative) instead of the
components:: prefix, which resolves to the latest released docs where
the ai-tool page does not exist yet (docs validation failure).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Croway
Croway force-pushed the CAMEL-24311-mcp-server-properties branch 2 times, most recently from 8608f05 to 028a66a Compare August 3, 2026 10:35
@Croway

Croway commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

All observations addressed (1–4 land on #25306 where that code was introduced; the @since nit here):

  1. Publish/unpublish race — the engine is now notified while holding the bridge lock, so map update and engine call are atomic per tool; no orphaned-tool window remains. Engine callbacks don't re-enter the bridge, so there's no deadlock exposure, and events are route-lifecycle-rate so the longer critical section is harmless.
  2. Timeout exchange accumulation — the WARN now logs the exchange id of the non-returned exchange for operators diagnosing memory pressure.
  3. Content-Type validation — POST without Content-Type: application/json is now rejected with 415 and a clear error, per the streamable HTTP spec.
  4. Unbounded initResult().block() — bounded with a 30s initialization timeout, consistent with the 5s notification timeout pattern.
  5. @since 4.22 on McpServerFactory — added.
  6. test-execution.md — kept as-is: it follows the existing convention of the sibling AI modules (camel-openai/test_execution.md, camel-langchain4j-agent/test-execution.md), so moving it would diverge from the pattern reviewers of those modules already know.

Both branches re-verified green (unit + conformance suites) and #25309 is rebased on the updated #25306.

Claude Code on behalf of Federico Mariani

Croway and others added 4 commits August 3, 2026 13:50
- Notify the engine while holding the bridge lock so publish/unpublish
  for the same tool cannot interleave and orphan a tool in the engine
- Log the exchange id when a timed-out call keeps its pooled exchange
- Reject POST requests without Content-Type application/json with 415
  (MCP streamable HTTP spec conformance)
- Bound session initialization with a 30s timeout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in autowiring

The MCP server now starts from properties alone, like Jolokia or
Prometheus - no code and no route for the server itself:

  camel.server.enabled = true
  camel.server.mcp-enabled = true
  camel.server.mcp-tags = crm,notify

- HttpServerConfigurationProperties: mcpEnabled, mcpTags,
  mcpToolTimeout (20s), mcpPath (/mcp), mcpServerName options with
  regenerated configurer, main metadata and main.adoc.
- McpServerFactory SPI in camel-main (mirrors MainHttpServerFactory);
  BaseMainSupport resolves it via bootstrap FactoryFinder when
  mcp-enabled=true and adds the bridge after the HTTP server so the
  engine finds the running router. With the HTTP server disabled the
  bridge still starts and fails fast with an actionable message.
- DefaultMcpServerFactory in camel-mcp-server maps the options onto
  McpServerConfiguration.
- Docs (CAMEL-24314): properties-based quick start replaces the
  programmatic example as primary; options table now names the
  camel.server.mcp-* properties.
- Catalog harvest for the new modules (others.properties,
  others/mcp-server.json, docs copies).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…endencies

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

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.

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.

2 participants