[
https://issues.apache.org/jira/browse/CAMEL-24308?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18101269#comment-18101269
]
Federico Mariani commented on CAMEL-24308:
------------------------------------------
Design revision 2: the runtime-agnostic a2a-pattern default engine is dropped
in favour of a *native engine per runtime*; the bridge + {{McpServerEngine}}
SPI are unchanged.
* *Camel Main / JBang*: {{camel-mcp-server-engine-vertx}} — MCP Java SDK with a
custom Vert.x-native {{McpStreamableServerTransportProvider}} registered on the
main HTTP server router (serves on {{camel.server.port}}). A custom transport
is unavoidable — the SDK ships only servlet and stdio server transports — but
this one is much simpler than the previously planned
RestConsumerFactory/queue-backed-InputStream design: direct async SSE writes,
while sessions, replay and message routing stay in the SDK's
{{McpStreamableServerSession}}.
* *Spring Boot*: engine in the camel-spring-boot repo over the Spring AI 2.x
MCP server starter ({{spring-ai-starter-mcp-server-webmvc}},
{{spring.ai.mcp.server.protocol=STREAMABLE}}) — zero transport code. Camel main
is already on Spring Boot 4.1, matching Spring AI 2.x's baseline.
{{spring.ai.mcp.server.*}} wins for serving concerns, same precedence rule as
Quarkus.
* *Quarkus*: quarkiverse quarkus-mcp-server engine, unchanged.
Consequences: no queue-backed SSE bridging anywhere; the Spring Boot servlet
SSE IT in CAMEL-24313 is superseded by running the engine conformance kit
against the Spring engine — the kit becomes the cross-engine behavioural
contract. Trade-off: no runtime-agnostic fallback, enabled-but-no-engine fails
startup with a clear message (a RestConsumerFactory-based engine can be added
later behind the same SPI). Description and CAMEL-24310/24311/24313 updated
accordingly.
_Claude Code on behalf of Federico Mariani_
> camel-mcp-server - expose ai-tool routes as MCP tools over streamable HTTP
> --------------------------------------------------------------------------
>
> Key: CAMEL-24308
> URL: https://issues.apache.org/jira/browse/CAMEL-24308
> Project: Camel
> Issue Type: New Feature
> Components: camel-ai
> Reporter: Federico Mariani
> Assignee: Federico Mariani
> Priority: Major
> Labels: ai, mcp
>
> h2. Goal
> Add a new {{camel-mcp-server}} module that exposes Camel routes registered
> via the {{ai-tool}} component (CAMEL-23382) as MCP tools, served over MCP
> streamable HTTP. No route is needed for the server itself — add the
> dependency, configure via properties, done.
> Tools are discovered from the shared {{AiToolRegistry}} by tag and invoked
> through {{AiToolExecutor}}, the same contract used by the langchain4j-agent
> and spring-ai-chat adapters. The MCP server is simply a third adapter over
> the same registry.
> h2. Example usage
> The intended user experience: no code and no route for the server itself — it
> behaves like Jolokia or Prometheus. Add the dependency, set a few properties,
> and every {{ai-tool}} route with a matching tag becomes an MCP tool that any
> MCP client (another Camel app, an IDE, a coding agent) can discover and call.
> Add the dependency:
> {code:xml}
> <dependency>
> <groupId>org.apache.camel</groupId>
> <artifactId>camel-mcp-server</artifactId>
> </dependency>
> {code}
> Configure via properties:
> {code}
> camel.server.mcp-enabled=true
> camel.server.mcp-path=/mcp
> camel.server.mcp-server-name=my-integration-app
> camel.server.mcp-tags=crm,notify
> {code}
> Define tools as regular {{ai-tool}} routes:
> {code}
> - route:
> from:
> uri: "ai-tool:query_db"
> parameters:
> description: "Query customer database"
> tags: "crm"
> parameter.customerId: string
> parameter.customerId.description: "The customer id"
> parameter.customerId.required: "true"
> steps:
> - to: "jdbc:dataSource"
> - route:
> from:
> uri: "ai-tool:send_email"
> parameters:
> description: "Send email notification"
> tags: "notify"
> parameter.to: string
> parameter.subject: string
> steps:
> - to: "smtp://mail.example.com"
> {code}
> Tools whose tags match {{mcp-tags}} are automatically exposed via streamable
> HTTP at {{http://localhost:8080/mcp}}. Any MCP client can then connect, e.g.
> another Camel integration using the camel-openai MCP client:
> {code:java}
> from("direct:agent")
> .to("openai:chat-completion"
> + "?model={{llm.model}}"
> + "&autoToolExecution=true"
> + "&mcpServer.myCamelTools.transportType=streamableHttp"
> + "&mcpServer.myCamelTools.url=http://localhost:8080/mcp");
> {code}
> or a local coding agent / IDE configured against the same URL.
> h2. Architecture: bridge + pluggable serving engine
> The module is split so that the *tool semantics* are shared across all
> runtimes while the *serving layer* is pluggable per runtime (mirroring the
> {{PlatformHttpEngine}} precedent):
> * {{camel-mcp-server}} — runtime-agnostic *bridge* + {{McpServerEngine}} SPI
> + configuration properties. NO dependency on the MCP Java SDK or
> platform-http (enforced at build level). The bridge owns tool selection
> (tags), the security policy, execution via {{AiToolExecutor}} (timeout, error
> sanitization) and reacts to {{AiToolRegistry}} listener events.
> * {{camel-mcp-server-engine-vertx}} — engine for Camel Main / JBang: official
> MCP Java SDK ({{io.modelcontextprotocol.sdk:mcp-core}} +
> {{mcp-json-jackson2}}, already used by camel-openai as MCP client) with a
> custom Vert.x-native streamable HTTP transport
> ({{McpStreamableServerTransportProvider}}) registered on the main HTTP
> server's router ({{VertxPlatformHttpRouter}}), serving on
> {{camel.server.port}}. The SDK ships no non-servlet HTTP server transport, so
> this transport is implemented here; the SDK's {{McpStreamableServerSession}}
> still owns session management, resumability ({{Last-Event-ID}} replay) and
> message routing — the transport only parses requests and writes SSE frames
> asynchronously. Declared as a *runtime* dependency of {{camel-mcp-server}} so
> plain Camel users need a single dependency.
> The engine is not "a server that Camel configures" — it is a *sink that Camel
> publishes tools into*. The SPI is intentionally small:
> {code:java}
> public interface McpServerEngine extends Service, CamelContextAware {
> void initialize(McpServerInfo info); // identity hint; native engines
> MAY ignore
> void toolAdded(McpServerTool tool); // initial set + later route starts
> void toolRemoved(String toolName); // route stopped/suspended ->
> list_changed
> }
> public interface McpServerTool {
> String name();
> String description();
> String inputSchemaJson(); // pre-built JSON Schema
> Map<String, ParameterDef> parameters(); // structured alternative
> McpToolCallHandler handler(); // blocking; timeout +
> sanitization already applied
> }
> public record McpToolCallResult(String text, boolean isError) { }
> {code}
> {{handler()}} returns a *pre-sanitized* result: the bridge maps
> {{AiToolResult}} to safe output before the engine ever sees it, so no engine
> implementation can leak route internals. Contract scope: single logical MCP
> server per CamelContext.
> Engine resolution: (1) a bean of type {{McpServerEngine}} in the Camel
> registry wins; (2) otherwise FactoryFinder locates the default engine on the
> classpath; (3) enabled-but-no-engine fails startup with a clear message.
> h2. Runtimes
> ||Runtime||User adds||Protocol impl||HTTP serving||MCP SDK / Reactor on
> classpath||
> |Camel Main / JBang|{{camel-mcp-server}} (vertx engine comes as runtime
> dep)|MCP Java SDK (vertx engine)|Vert.x main HTTP server
> ({{camel.server.port}})|yes|
> |Spring Boot|{{camel-mcp-server-starter}}|Spring AI 2.x MCP server starter
> (native engine)|WebMVC (async servlet) or WebFlux|via Spring AI|
> |Quarkus|{{camel-quarkus-mcp-server}}|quarkiverse quarkus-mcp-server (native
> engine)|Quarkus HTTP|no|
> * *Spring Boot* — the camel-spring-boot starter provides an engine over
> Spring AI 2.x's auto-configured {{McpSyncServer}}
> ({{spring-ai-starter-mcp-server-webmvc}},
> {{spring.ai.mcp.server.protocol=STREAMABLE}}) and excludes the vertx engine.
> Spring AI 2.x requires Spring Boot 4.1, which Camel {{main}} already targets.
> Config precedence: {{spring.ai.mcp.server.*}} wins for serving concerns.
> * *Quarkus* — the camel-quarkus extension (tracked in the camel-quarkus repo)
> provides a {{McpServerEngine}} backed by quarkus-mcp-server's programmatic
> {{ToolManager}} API and excludes the vertx engine — no MCP Java SDK / Reactor
> on the Quarkus classpath. Config precedence: {{quarkus.mcp.server.*}} wins
> for serving concerns — see CAMEL-24311.
> * There is deliberately no runtime-agnostic fallback engine:
> enabled-but-no-engine (e.g. camel-main without the Vert.x main HTTP server)
> fails startup with a clear message. A {{RestConsumerFactory}}-based engine
> can be added later behind the same SPI if needed.
> Note on SDK neutrality: the MCP Java SDK restructuring discussed in
> [modelcontextprotocol/discussions/246|https://github.com/orgs/modelcontextprotocol/discussions/246]
> (framework-agnostic {{mcp-core}}, pluggable JSON, Spring code moved out to
> Spring AI) is what makes the default engine Spring-free; camel-openai already
> consumes the restructured artifacts.
> h2. Protocol layer (vertx engine)
> POST answering {{application/json}} or {{text/event-stream}}, long-lived GET
> SSE channel for server notifications with {{Last-Event-ID}} replay,
> {{Mcp-Session-Id}} session management, DELETE for session termination —
> session semantics provided by the SDK's {{McpStreamableServerSession}}. On
> Spring Boot and Quarkus the native stacks provide the equivalent protocol
> layer.
> A stdio transport (SDK built-in) should follow as a separate issue for
> camel-jbang local development (expose a Camel route as a tool for a local
> coding agent).
> h2. Tool semantics and security (bridge — identical on every runtime)
> * Tools whose tags intersect {{mcp-tags}} are exposed. The untagged default
> pool is NOT exposed implicitly — external MCP clients are untrusted senders
> and crossing that trust boundary must be an explicit opt-in per tool (tag it).
> * MCP has a flat tool namespace: fail fast at startup (or refuse the tool
> with a loud warning) on name collisions across the selected tags instead of
> silent first-wins.
> * {{tools/list_changed}}: requires adding a listener SPI to
> {{AiToolRegistry}} (register/deregister callbacks) so the bridge can push
> {{toolAdded}}/{{toolRemoved}} to the engine when routes start/stop/suspend.
> Small prerequisite change in camel-ai-tool.
> * Map {{AiToolResult}} to {{CallToolResult}} *in the bridge*:
> {{ArgumentError}} -> isError=true with the validation message;
> {{ExecutionError}} -> isError=true with a GENERIC message only (per the
> {{AiToolResult}} security note, raw route exception messages must not leak to
> remote clients; log the cause server-side).
> * Per-call execution timeout ({{camel.server.mcp-tool-timeout}}, default e.g.
> 20s) — {{AiToolExecutor.execute}} is synchronous and unbounded; a hanging
> route must not hold an MCP request open forever.
> * Authentication: default engine documents wiring via platform-http
> authentication and camel-oauth (MCP auth model is OAuth 2.1 resource server),
> mirroring the {{oauthProfile}} idiom already used by the camel-openai MCP
> client. On Quarkus, authentication is owned by quarkus-mcp-server / Quarkus
> security.
> h2. Out of scope (follow-up issues)
> * stdio transport for camel-jbang.
> * The Quarkus engine implementation itself — lives in the camel-quarkus repo
> (planned, tracked there), together with the {{camel-quarkus-ai-tool}}
> extension.
> * Raw JSON Schema tool input ({{argSchema}}) in camel-ai-tool — currently
> only the flat {{parameter.NAME=type}} syntax exists; nested schemas are a
> common MCP need. Note the executor argument allowlist must derive property
> names from the raw schema (same bug class as CAMEL-24241).
> * MCP tool annotations (readOnlyHint/destructiveHint/idempotentHint) as
> optional ai-tool endpoint metadata.
> * Structured content / outputSchema ({{AiToolResult}} is string-only today).
> The implementation is broken down into the attached sub-tasks.
> ----
> _This issue was drafted by Claude Code on behalf of Federico Mariani._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)