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

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

Update after design discussion — the issue description and sub-tasks have been 
reworked around a *pluggable serving engine*:

* {{camel-mcp-server}} is now split into a runtime-agnostic bridge + small 
{{McpServerEngine}} SPI (tool selection, security policy, execution, 
sanitization — identical on every runtime) and a default engine ({{mcp-core}} + 
platform-http/A2A-style transport). Resolution mirrors {{PlatformHttpEngine}}: 
registry bean wins, FactoryFinder finds the default.
* *Quarkus*: per the team's preference to reuse quarkiverse quarkus-mcp-server, 
the camel-quarkus extension will provide an engine backed by its programmatic 
{{ToolManager}} API and exclude the default engine — no MCP Java SDK / Reactor 
on the Quarkus classpath, and Quarkus users get dev UI, guardrails, CDI and 
native image support natively. The {{AiToolRegistry}} listener (CAMEL-24309) is 
the integration point for dynamic tool add/remove.
* *Config precedence on Quarkus*: {{quarkus.mcp.server.*}} wins for serving 
concerns (path, transports, auth); bridge properties ({{mcp-enabled}}, 
{{mcp-tags}}, {{mcp-tool-timeout}}) stay Camel-owned everywhere. Engine-owned 
Camel properties that the resolved engine does not consume produce a startup 
WARN. Details in CAMEL-24311.
* *Spring Boot* stays on the default engine via {{camel-platform-http-starter}} 
(servlet); the SSE verification IT is tracked in CAMEL-24313.
* Context on SDK neutrality: after 
[modelcontextprotocol/discussions/246|https://github.com/orgs/modelcontextprotocol/discussions/246]
 the SDK core is framework-agnostic ({{mcp-core}}, pluggable JSON, Spring bits 
moved to Spring AI) — camel-openai already consumes the restructured artifacts, 
so the default engine carries no Spring dependency.

_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
>            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-default}} — default engine: official MCP Java SDK 
> ({{io.modelcontextprotocol.sdk:mcp-core}} + {{mcp-json-jackson2}}, already 
> used by camel-openai as MCP client) with a streamable HTTP transport built on 
> platform-http following the *camel-a2a pattern*: {{RestConsumerFactory}} 
> discovery, routes registered with {{useStreaming=true}}, SSE via queue-backed 
> {{InputStream}} emitter (see {{A2AConsumer}}, {{SseQueueInputStream}}, 
> {{A2AStreamEmitter}}). Real-time SSE through platform-http is verified on 
> Vert.x since CAMEL-23804. 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}} (+ {{camel-platform-http-vertx}}, 
> auto with JBang)|MCP Java SDK (default engine)|Vert.x main server (verified 
> SSE)|yes|
> |Spring Boot|{{camel-mcp-server-starter}}|MCP Java SDK (default 
> engine)|servlet container via {{camel-platform-http-starter}}|yes|
> |Quarkus|{{camel-quarkus-mcp-server}}|quarkiverse quarkus-mcp-server (native 
> engine)|Quarkus HTTP|no|
> * *Spring Boot* — same default engine; the servlet binding flushes per write 
> ({{DefaultHttpBinding.copyStream}}) so SSE should work, but it is not on the 
> verified list: an IT asserting events arrive before stream completion is 
> required (in the camel-spring-boot repo). Long-lived streams pin a servlet 
> thread — document this.
> * *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 default engine, so the MCP Java SDK 
> and Reactor never reach the Quarkus classpath. Quarkus users get the native 
> ecosystem (dev UI, guardrails, CDI, native image) for free. Config precedence 
> on Quarkus: {{quarkus.mcp.server.*}} wins for serving concerns — see the 
> configuration ownership rules in CAMEL-24311.
> * Deliberately NOT using the {{PlatformHttpPlugin}} SPI (jolokia-style) for 
> the default engine: its handler is a Vert.x {{Handler<RoutingContext>}} in 
> practice, which would exclude the Spring Boot servlet runtime.
> 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 (default engine)
> POST answering {{application/json}} or {{text/event-stream}}, long-lived GET 
> SSE channel for server notifications, {{Mcp-Session-Id}} session management, 
> DELETE for session termination.
> 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