[ 
https://issues.apache.org/jira/browse/CAMEL-24308?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18101418#comment-18101418
 ] 

Federico Mariani commented on CAMEL-24308:
------------------------------------------

The Quarkus engine is implemented: 
[camel-quarkus#8949|https://github.com/apache/camel-quarkus/issues/8949] / [PR 
#8950|https://github.com/apache/camel-quarkus/pull/8950] (draft, targets the 
camel-main branch) — JVM-only {{camel-quarkus-mcp-server}} extension over the 
quarkiverse quarkus-mcp-server {{ToolManager}} API, depending on 
{{camel-mcp-server-api}} only (no MCP Java SDK / Reactor on the Quarkus 
classpath). All engine conformance scenarios green with the official MCP SDK 
client. With this, all three runtimes from the design are implemented: Vert.x 
(camel PR #25306), Spring Boot (camel-spring-boot PR #1871), Quarkus 
(camel-quarkus PR #8950).

_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)

Reply via email to