[ 
https://issues.apache.org/jira/browse/CAMEL-24308?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Federico Mariani updated CAMEL-24308:
-------------------------------------
    Description: 
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._

  was:
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._


> 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