CAMEL-24311: camel.server.mcp-* configuration properties and camel-main autowiring - #25309
CAMEL-24311: camel.server.mcp-* configuration properties and camel-main autowiring#25309Croway wants to merge 11 commits into
Conversation
…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>
gnodet
left a comment
There was a problem hiding this comment.
🤖 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 addingblock(Duration.ofSeconds(30))consistent with the notification timeout pattern used elsewhere.McpServerFactoryincore/camel-main— missing@since 4.22Javadoc tag. While the convention is primarily forcore/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. 👍
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>
8608f05 to
028a66a
Compare
|
All observations addressed (1–4 land on #25306 where that code was introduced; the
Both branches re-verified green (unit + conformance suites) and #25309 is rebased on the updated #25306. Claude Code on behalf of Federico Mariani |
- 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>
028a66a to
f1a0712
Compare
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
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.
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:
HttpServerConfigurationProperties:mcpEnabled(false),mcpTags,mcpToolTimeout(20s),mcpPath(/mcp),mcpServerName— with fluent withers; configurer,camel-main-configuration-metadata.jsonandmain.adocregenerated.McpServerFactorySPI in camel-main, mirroringMainHttpServerFactory:BaseMainSupportresolves it (registry bean → bootstrap FactoryFinder keymcp-server, jar hintcamel-mcp-server) whenmcp-enabled=trueand adds the bridge after the HTTP server service, so the Vert.x engine finds the running router. Withcamel.server.enabled=falsethe bridge is still set up so startup fails fast with the engine's actionable message instead of silently doing nothing.DefaultMcpServerFactory(@JdkService) incamel-mcp-servermaps the options ontoMcpServerConfiguration. It lives in the engine module deliberately: that module is main/JBang-only, socamel-mainnever reaches the Quarkus/Spring Boot classpath throughcamel-mcp-server-api(precedent:camel-platform-http-mainimplementingMainHttpServerFactory).camel.server.mcp-*properties per the bridge/engine ownership split.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:org.apache.camel.main.Mainfrom initial properties only, then verifies with the official MCP SDK client: advertised server name, tag-filteredtools/list(untagged pool excluded), successfultools/call.mcp-enabled=truewithout 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