From 4af1dddbc858060277c646991e4ec6fb6f09acfa Mon Sep 17 00:00:00 2001 From: croway Date: Mon, 3 Aug 2026 12:07:48 +0200 Subject: [PATCH 1/3] CAMEL-24311: camel.server.mcp-* configuration properties and camel-main 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 --- .../org/apache/camel/catalog/docs/main.adoc | 7 +- .../apache/camel/catalog/docs/mcp-server.adoc | 50 +++++--- .../camel-main-configuration-metadata.json | 5 + components/camel-ai/camel-mcp-server/pom.xml | 4 + .../services/org/apache/camel/mcp-server | 2 + .../src/main/docs/mcp-server.adoc | 44 ++++--- .../server/main/DefaultMcpServerFactory.java | 44 +++++++ .../main/McpServerMainPropertiesTest.java | 103 ++++++++++++++++ ...rverConfigurationPropertiesConfigurer.java | 35 ++++++ .../camel-main-configuration-metadata.json | 5 + core/camel-main/src/main/docs/main.adoc | 7 +- .../apache/camel/main/BaseMainSupport.java | 28 ++++- .../HttpServerConfigurationProperties.java | 112 ++++++++++++++++++ .../org/apache/camel/main/MainConstants.java | 1 + .../apache/camel/main/McpServerFactory.java | 37 ++++++ 15 files changed, 450 insertions(+), 34 deletions(-) create mode 100644 components/camel-ai/camel-mcp-server/src/generated/resources/META-INF/services/org/apache/camel/mcp-server create mode 100644 components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/DefaultMcpServerFactory.java create mode 100644 components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java create mode 100644 core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc index 352d5a8e8fd59..ffa14c8dfd9db 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc @@ -195,7 +195,7 @@ The camel.routecontroller supports 12 options, which are listed below. === Camel Embedded HTTP Server (only for standalone; not Spring Boot or Quarkus) configurations -The camel.server supports 21 options, which are listed below. +The camel.server supports 26 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -215,6 +215,11 @@ The camel.server supports 21 options, which are listed below. | *camel.server.jwtKeystorePath* | Path to the keystore file used for JWT tokens validation. | | String | *camel.server.jwtKeystoreType* | Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.). | | String | *camel.server.maxBodySize* | Maximum HTTP body size the embedded HTTP server can accept. | | Long +| *camel.server.mcpEnabled* | Whether to expose ai-tool routes as MCP tools over streamable HTTP. Requires camel-mcp-server on the classpath. By default, the MCP server is not enabled. | false | boolean +| *camel.server.mcpPath* | HTTP path where the MCP endpoint is served. | /mcp | String +| *camel.server.mcpServerName* | MCP server name advertised to clients. Defaults to the CamelContext name. | | String +| *camel.server.mcpTags* | Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. | | String +| *camel.server.mcpToolTimeout* | Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own. | 20000 | long | *camel.server.path* | Context-path to use for embedded HTTP server | / | String | *camel.server.port* | Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number. | 8080 | int | *camel.server.staticContextPath* | The context-path to use for serving static content. By default, the root path is used. And if there is an index.html page then this is automatically loaded. | / | String diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc index 6b28142ca981a..4d438625d8756 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc @@ -10,7 +10,7 @@ *Since Camel {since}* The camel-mcp-server module exposes Camel routes registered via the -xref:ROOT:ai-tool-component.adoc[ai-tool] component as tools of a +xref:components::ai-tool-component.adoc[ai-tool] component as tools of a https://modelcontextprotocol.io[Model Context Protocol] (MCP) server, served over MCP streamable HTTP. No route is needed for the server itself: add the dependency, configure which tags to expose, and every matching `ai-tool` route @@ -101,8 +101,19 @@ YAML:: ---- ==== -Start the MCP server by adding the `McpServerBridge` service to the -CamelContext, selecting the tags to expose: +On Camel Main and Camel JBang no code is needed — like Jolokia or +Prometheus, the server starts from configuration properties alone: + +[source,properties] +---- +camel.server.enabled = true +camel.server.mcp-enabled = true +camel.server.mcp-tags = crm,notify +camel.server.mcp-server-name = my-integration-app +---- + +On other runtimes, or when wiring programmatically, add the +`McpServerBridge` service to the CamelContext instead: [source,java] ---- @@ -114,7 +125,7 @@ camelContext.addService(new McpServerBridge(configuration)); The MCP endpoint is then served at `http://:/mcp` on the Camel main HTTP server. Any MCP client can connect over streamable HTTP, for example another Camel integration using the -xref:ROOT:openai-component.adoc[camel-openai] MCP client: +xref:components::openai-component.adoc[camel-openai] MCP client: [source,java] ---- @@ -128,23 +139,28 @@ from("direct:agent") == Options -The `McpServerConfiguration` options: +The options, configurable as `camel.server.mcp-*` properties on Camel Main / +JBang (see the xref:main.adoc[camel-main] options) or on +`McpServerConfiguration` programmatically: [width="100%",cols="2,5,2,1",options="header"] |=== | Option | Description | Default | Owner -| `tags` | Comma-separated list of ai-tool tags to expose as MCP tools. Only - tools registered under one of these tags are published; the untagged - default pool is never exposed. When not set, no tools are published. | | - bridge -| `toolTimeout` | Per-call tool execution timeout in milliseconds. A call - exceeding the timeout returns an error result to the MCP client; the - underlying route keeps running until it completes on its own. | `20000` | - bridge -| `path` | HTTP path where the MCP endpoint is served. | `/mcp` | engine -| `serverName` | MCP server name advertised to clients. | CamelContext name | - engine +| `camel.server.mcp-enabled` | Whether to expose ai-tool routes as MCP tools + over streamable HTTP. | `false` | bridge +| `camel.server.mcp-tags` | Comma-separated list of ai-tool tags to expose as + MCP tools. Only tools registered under one of these tags are published; the + untagged default pool is never exposed. When not set, no tools are + published. | | bridge +| `camel.server.mcp-tool-timeout` | Per-call tool execution timeout in + milliseconds. A call exceeding the timeout returns an error result to the + MCP client; the underlying route keeps running until it completes on its + own. | `20000` | bridge +| `camel.server.mcp-path` | HTTP path where the MCP endpoint is served. | + `/mcp` | engine +| `camel.server.mcp-server-name` | MCP server name advertised to clients. | + CamelContext name | engine |=== Bridge-owned options are honored identically on every runtime. Engine-owned @@ -193,7 +209,7 @@ following rules: router, so platform-http authentication (basic, JWT via `camel.server.authentication*` options) applies to it. The MCP specification's authorization model is OAuth 2.1; see - xref:oauth.adoc[camel-oauth] for resource-server style + xref:components:others:oauth.adoc[camel-oauth] for resource-server style protection. On Quarkus and Spring Boot, authentication is owned by the native runtime security. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json index 4c5a8c43a5d7b..5630025faa29c 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json @@ -437,6 +437,11 @@ { "name": "camel.server.jwtKeystorePath", "required": false, "description": "Path to the keystore file used for JWT tokens validation.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.jwtKeystoreType", "required": false, "description": "Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.).", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.maxBodySize", "required": false, "description": "Maximum HTTP body size the embedded HTTP server can accept.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "java.lang.Long", "secret": false }, + { "name": "camel.server.mcpEnabled", "required": false, "description": "Whether to expose ai-tool routes as MCP tools over streamable HTTP. Requires camel-mcp-server on the classpath. By default, the MCP server is not enabled.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, + { "name": "camel.server.mcpPath", "required": false, "description": "HTTP path where the MCP endpoint is served.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/mcp", "secret": false }, + { "name": "camel.server.mcpServerName", "required": false, "description": "MCP server name advertised to clients. Defaults to the CamelContext name.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.server.mcpTags", "required": false, "description": "Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.server.mcpToolTimeout", "required": false, "description": "Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "long", "defaultValue": 20000, "secret": false }, { "name": "camel.server.path", "required": false, "description": "Context-path to use for embedded HTTP server", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/", "secret": false }, { "name": "camel.server.port", "required": false, "description": "Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 8080, "secret": false }, { "name": "camel.server.staticContextPath", "required": false, "description": "The context-path to use for serving static content. By default, the root path is used. And if there is an index.html page then this is automatically loaded.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/", "secret": false }, diff --git a/components/camel-ai/camel-mcp-server/pom.xml b/components/camel-ai/camel-mcp-server/pom.xml index 40db5a9dc0913..2adf333c000de 100644 --- a/components/camel-ai/camel-mcp-server/pom.xml +++ b/components/camel-ai/camel-mcp-server/pom.xml @@ -45,6 +45,10 @@ org.apache.camel camel-mcp-server-api + + org.apache.camel + camel-main + org.apache.camel camel-platform-http-vertx diff --git a/components/camel-ai/camel-mcp-server/src/generated/resources/META-INF/services/org/apache/camel/mcp-server b/components/camel-ai/camel-mcp-server/src/generated/resources/META-INF/services/org/apache/camel/mcp-server new file mode 100644 index 0000000000000..240ad12e3b063 --- /dev/null +++ b/components/camel-ai/camel-mcp-server/src/generated/resources/META-INF/services/org/apache/camel/mcp-server @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.mcp.server.main.DefaultMcpServerFactory diff --git a/components/camel-ai/camel-mcp-server/src/main/docs/mcp-server.adoc b/components/camel-ai/camel-mcp-server/src/main/docs/mcp-server.adoc index 6b28142ca981a..17e3b436e191b 100644 --- a/components/camel-ai/camel-mcp-server/src/main/docs/mcp-server.adoc +++ b/components/camel-ai/camel-mcp-server/src/main/docs/mcp-server.adoc @@ -101,8 +101,19 @@ YAML:: ---- ==== -Start the MCP server by adding the `McpServerBridge` service to the -CamelContext, selecting the tags to expose: +On Camel Main and Camel JBang no code is needed — like Jolokia or +Prometheus, the server starts from configuration properties alone: + +[source,properties] +---- +camel.server.enabled = true +camel.server.mcp-enabled = true +camel.server.mcp-tags = crm,notify +camel.server.mcp-server-name = my-integration-app +---- + +On other runtimes, or when wiring programmatically, add the +`McpServerBridge` service to the CamelContext instead: [source,java] ---- @@ -128,23 +139,28 @@ from("direct:agent") == Options -The `McpServerConfiguration` options: +The options, configurable as `camel.server.mcp-*` properties on Camel Main / +JBang (see the xref:main.adoc[camel-main] options) or on +`McpServerConfiguration` programmatically: [width="100%",cols="2,5,2,1",options="header"] |=== | Option | Description | Default | Owner -| `tags` | Comma-separated list of ai-tool tags to expose as MCP tools. Only - tools registered under one of these tags are published; the untagged - default pool is never exposed. When not set, no tools are published. | | - bridge -| `toolTimeout` | Per-call tool execution timeout in milliseconds. A call - exceeding the timeout returns an error result to the MCP client; the - underlying route keeps running until it completes on its own. | `20000` | - bridge -| `path` | HTTP path where the MCP endpoint is served. | `/mcp` | engine -| `serverName` | MCP server name advertised to clients. | CamelContext name | - engine +| `camel.server.mcp-enabled` | Whether to expose ai-tool routes as MCP tools + over streamable HTTP. | `false` | bridge +| `camel.server.mcp-tags` | Comma-separated list of ai-tool tags to expose as + MCP tools. Only tools registered under one of these tags are published; the + untagged default pool is never exposed. When not set, no tools are + published. | | bridge +| `camel.server.mcp-tool-timeout` | Per-call tool execution timeout in + milliseconds. A call exceeding the timeout returns an error result to the + MCP client; the underlying route keeps running until it completes on its + own. | `20000` | bridge +| `camel.server.mcp-path` | HTTP path where the MCP endpoint is served. | + `/mcp` | engine +| `camel.server.mcp-server-name` | MCP server name advertised to clients. | + CamelContext name | engine |=== Bridge-owned options are honored identically on every runtime. Engine-owned diff --git a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/DefaultMcpServerFactory.java b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/DefaultMcpServerFactory.java new file mode 100644 index 0000000000000..d524fa46fa20d --- /dev/null +++ b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/DefaultMcpServerFactory.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.mcp.server.main; + +import org.apache.camel.CamelContext; +import org.apache.camel.Service; +import org.apache.camel.component.mcp.server.McpServerBridge; +import org.apache.camel.component.mcp.server.McpServerConfiguration; +import org.apache.camel.main.HttpServerConfigurationProperties; +import org.apache.camel.main.MainConstants; +import org.apache.camel.main.McpServerFactory; +import org.apache.camel.spi.annotations.JdkService; + +/** + * {@link McpServerFactory} creating the {@link McpServerBridge} from the {@code camel.server.mcp-*} options, so the MCP + * server starts from properties alone on Camel Main / JBang. + */ +@JdkService(MainConstants.MCP_SERVER) +public class DefaultMcpServerFactory implements McpServerFactory { + + @Override + public Service newMcpServer(CamelContext camelContext, HttpServerConfigurationProperties configuration) { + McpServerConfiguration mcpConfiguration = new McpServerConfiguration(); + mcpConfiguration.setTags(configuration.getMcpTags()); + mcpConfiguration.setToolTimeout(configuration.getMcpToolTimeout()); + mcpConfiguration.setPath(configuration.getMcpPath()); + mcpConfiguration.setServerName(configuration.getMcpServerName()); + return new McpServerBridge(mcpConfiguration); + } +} diff --git a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java new file mode 100644 index 0000000000000..872d3bb7f93ba --- /dev/null +++ b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.mcp.server.main; + +import java.time.Duration; +import java.util.Map; + +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.spec.McpSchema; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.main.Main; +import org.apache.camel.test.AvailablePortFinder; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Verifies the camel-main autowiring (CAMEL-24311): the MCP server starts from {@code camel.server.mcp-*} properties + * alone — no code and no route for the server itself. + */ +class McpServerMainPropertiesTest { + + @Test + void testMcpServerStartsFromPropertiesAlone() throws Exception { + int port = AvailablePortFinder.getNextAvailable(); + + Main main = new Main(); + main.configure().addRoutesBuilder(new RouteBuilder() { + @Override + public void configure() { + from("ai-tool:say_hello?tags=crm&description=Say hello" + + "¶meter.name=string¶meter.name.required=true") + .setBody(simple("Hello ${header.name}")); + + from("ai-tool:hidden_tool?description=Untagged, not exposed") + .setBody(constant("hidden")); + } + }); + main.addInitialProperty("camel.server.enabled", "true"); + main.addInitialProperty("camel.server.port", String.valueOf(port)); + main.addInitialProperty("camel.server.mcp-enabled", "true"); + main.addInitialProperty("camel.server.mcp-tags", "crm"); + main.addInitialProperty("camel.server.mcp-server-name", "my-integration-app"); + main.start(); + + McpSyncClient client = null; + try { + client = McpClient.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + port).build()) + .requestTimeout(Duration.ofSeconds(10)) + .initializationTimeout(Duration.ofSeconds(10)) + .build(); + + McpSchema.InitializeResult init = client.initialize(); + assertThat(init.serverInfo().name()).isEqualTo("my-integration-app"); + + assertThat(client.listTools().tools()) + .extracting(McpSchema.Tool::name) + .contains("say_hello") + .doesNotContain("hidden_tool"); + + McpSchema.CallToolResult result + = client.callTool(new McpSchema.CallToolRequest("say_hello", Map.of("name", "Camel"))); + assertThat(result.isError()).isNotEqualTo(Boolean.TRUE); + assertThat(result.content().toString()).contains("Hello Camel"); + } finally { + if (client != null) { + client.closeGracefully(); + } + main.stop(); + } + } + + @Test + void testMcpEnabledWithoutHttpServerFailsFast() { + Main main = new Main(); + main.addInitialProperty("camel.server.mcp-enabled", "true"); + main.addInitialProperty("camel.server.mcp-tags", "crm"); + + try { + assertThatThrownBy(main::start) + .hasStackTraceContaining("Vert.x platform HTTP server"); + } finally { + main.stop(); + } + } +} diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java index 7c1d68afe45da..9129b2b45a812 100644 --- a/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java +++ b/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java @@ -37,6 +37,11 @@ public class HttpServerConfigurationPropertiesConfigurer extends org.apache.came map.put("JwtKeystorePath", java.lang.String.class); map.put("JwtKeystoreType", java.lang.String.class); map.put("MaxBodySize", java.lang.Long.class); + map.put("McpEnabled", boolean.class); + map.put("McpPath", java.lang.String.class); + map.put("McpServerName", java.lang.String.class); + map.put("McpTags", java.lang.String.class); + map.put("McpToolTimeout", long.class); map.put("Path", java.lang.String.class); map.put("Port", int.class); map.put("StaticContextPath", java.lang.String.class); @@ -78,6 +83,16 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "jwtKeystoreType": target.setJwtKeystoreType(property(camelContext, java.lang.String.class, value)); return true; case "maxbodysize": case "maxBodySize": target.setMaxBodySize(property(camelContext, java.lang.Long.class, value)); return true; + case "mcpenabled": + case "mcpEnabled": target.setMcpEnabled(property(camelContext, boolean.class, value)); return true; + case "mcppath": + case "mcpPath": target.setMcpPath(property(camelContext, java.lang.String.class, value)); return true; + case "mcpservername": + case "mcpServerName": target.setMcpServerName(property(camelContext, java.lang.String.class, value)); return true; + case "mcptags": + case "mcpTags": target.setMcpTags(property(camelContext, java.lang.String.class, value)); return true; + case "mcptooltimeout": + case "mcpToolTimeout": target.setMcpToolTimeout(property(camelContext, long.class, value)); return true; case "path": target.setPath(property(camelContext, java.lang.String.class, value)); return true; case "port": target.setPort(property(camelContext, int.class, value)); return true; case "staticcontextpath": @@ -128,6 +143,16 @@ public Class getOptionType(String name, boolean ignoreCase) { case "jwtKeystoreType": return java.lang.String.class; case "maxbodysize": case "maxBodySize": return java.lang.Long.class; + case "mcpenabled": + case "mcpEnabled": return boolean.class; + case "mcppath": + case "mcpPath": return java.lang.String.class; + case "mcpservername": + case "mcpServerName": return java.lang.String.class; + case "mcptags": + case "mcpTags": return java.lang.String.class; + case "mcptooltimeout": + case "mcpToolTimeout": return long.class; case "path": return java.lang.String.class; case "port": return int.class; case "staticcontextpath": @@ -174,6 +199,16 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "jwtKeystoreType": return target.getJwtKeystoreType(); case "maxbodysize": case "maxBodySize": return target.getMaxBodySize(); + case "mcpenabled": + case "mcpEnabled": return target.isMcpEnabled(); + case "mcppath": + case "mcpPath": return target.getMcpPath(); + case "mcpservername": + case "mcpServerName": return target.getMcpServerName(); + case "mcptags": + case "mcpTags": return target.getMcpTags(); + case "mcptooltimeout": + case "mcpToolTimeout": return target.getMcpToolTimeout(); case "path": return target.getPath(); case "port": return target.getPort(); case "staticcontextpath": diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index 4c5a8c43a5d7b..5630025faa29c 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -437,6 +437,11 @@ { "name": "camel.server.jwtKeystorePath", "required": false, "description": "Path to the keystore file used for JWT tokens validation.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.jwtKeystoreType", "required": false, "description": "Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.).", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.maxBodySize", "required": false, "description": "Maximum HTTP body size the embedded HTTP server can accept.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "java.lang.Long", "secret": false }, + { "name": "camel.server.mcpEnabled", "required": false, "description": "Whether to expose ai-tool routes as MCP tools over streamable HTTP. Requires camel-mcp-server on the classpath. By default, the MCP server is not enabled.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false }, + { "name": "camel.server.mcpPath", "required": false, "description": "HTTP path where the MCP endpoint is served.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/mcp", "secret": false }, + { "name": "camel.server.mcpServerName", "required": false, "description": "MCP server name advertised to clients. Defaults to the CamelContext name.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.server.mcpTags", "required": false, "description": "Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.server.mcpToolTimeout", "required": false, "description": "Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "long", "defaultValue": 20000, "secret": false }, { "name": "camel.server.path", "required": false, "description": "Context-path to use for embedded HTTP server", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/", "secret": false }, { "name": "camel.server.port", "required": false, "description": "Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 8080, "secret": false }, { "name": "camel.server.staticContextPath", "required": false, "description": "The context-path to use for serving static content. By default, the root path is used. And if there is an index.html page then this is automatically loaded.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "\/", "secret": false }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 352d5a8e8fd59..ffa14c8dfd9db 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -195,7 +195,7 @@ The camel.routecontroller supports 12 options, which are listed below. === Camel Embedded HTTP Server (only for standalone; not Spring Boot or Quarkus) configurations -The camel.server supports 21 options, which are listed below. +The camel.server supports 26 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -215,6 +215,11 @@ The camel.server supports 21 options, which are listed below. | *camel.server.jwtKeystorePath* | Path to the keystore file used for JWT tokens validation. | | String | *camel.server.jwtKeystoreType* | Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.). | | String | *camel.server.maxBodySize* | Maximum HTTP body size the embedded HTTP server can accept. | | Long +| *camel.server.mcpEnabled* | Whether to expose ai-tool routes as MCP tools over streamable HTTP. Requires camel-mcp-server on the classpath. By default, the MCP server is not enabled. | false | boolean +| *camel.server.mcpPath* | HTTP path where the MCP endpoint is served. | /mcp | String +| *camel.server.mcpServerName* | MCP server name advertised to clients. Defaults to the CamelContext name. | | String +| *camel.server.mcpTags* | Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are exposed; the untagged default pool is never exposed. When not set, no tools are exposed. | | String +| *camel.server.mcpToolTimeout* | Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the MCP client; the underlying route keeps running until it completes on its own. | 20000 | long | *camel.server.path* | Context-path to use for embedded HTTP server | / | String | *camel.server.port* | Port to use for binding embedded HTTP server. Use 0 to dynamic assign a free random port number. | 8080 | int | *camel.server.staticContextPath* | The context-path to use for serving static content. By default, the root path is used. And if there is an index.html page then this is automatically loaded. | / | String diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index d9560638622f2..b9205a0a73b0a 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -2188,7 +2188,9 @@ private void setHttpServerProperties( mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties); if (!server.isEnabled()) { - // http server is disabled + // http server is disabled; the mcp server (if enabled) is still set up so it can + // fail-fast with an actionable message about the missing http server + setupMcpServer(camelContext, server); return; } @@ -2207,6 +2209,20 @@ private void setHttpServerProperties( // force eager starting as embedded http server is used for // container platform to check readiness and need to be started eager camelContext.addService(http, true, true); + + // the mcp server serves through the http server, so it is set up (and started) after it + setupMcpServer(camelContext, server); + } + + private void setupMcpServer(CamelContext camelContext, HttpServerConfigurationProperties server) throws Exception { + if (!server.isMcpEnabled()) { + return; + } + // auto-detect camel-mcp-server on classpath + McpServerFactory factory = resolveMcpServerFactory(camelContext); + Service mcp = factory.newMcpServer(camelContext, server); + // force eager starting so the mcp endpoint is served as soon as the http server is up + camelContext.addService(mcp, true, true); } private void setHttpManagementServerProperties( @@ -3179,6 +3195,16 @@ private static MainHttpServerFactory resolveMainHttpServerFactory(CamelContext c return CamelContextAware.trySetCamelContext(answer, camelContext); } + private static McpServerFactory resolveMcpServerFactory(CamelContext camelContext) { + // lookup in service registry first + McpServerFactory answer = camelContext.getRegistry().findSingleByType(McpServerFactory.class); + if (answer == null) { + answer = ResolverHelper.resolveMandatoryBootstrapService(camelContext, MainConstants.MCP_SERVER, + McpServerFactory.class, "camel-mcp-server"); + } + return CamelContextAware.trySetCamelContext(answer, camelContext); + } + private static final class PropertyPlaceholderListener implements PropertiesLookupListener { private final OrderedLocationProperties olp; diff --git a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java index c153b60d1d3f5..da2603c3cce36 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java @@ -71,6 +71,17 @@ public class HttpServerConfigurationProperties implements BootstrapCloseable { @Metadata(label = "security", defaultValue = "false", security = "insecure:dev") private boolean jwtAllowMissingIssuerAndAudience; + @Metadata + private boolean mcpEnabled; + @Metadata + private String mcpTags; + @Metadata(defaultValue = "20000") + private long mcpToolTimeout = 20000; + @Metadata(defaultValue = "/mcp") + private String mcpPath = "/mcp"; + @Metadata + private String mcpServerName; + public HttpServerConfigurationProperties(MainConfigurationProperties parent) { this.parent = parent; } @@ -326,6 +337,64 @@ public void setJwtAllowMissingIssuerAndAudience(boolean jwtAllowMissingIssuerAnd this.jwtAllowMissingIssuerAndAudience = jwtAllowMissingIssuerAndAudience; } + public boolean isMcpEnabled() { + return mcpEnabled; + } + + /** + * Whether to expose ai-tool routes as MCP tools over streamable HTTP. Requires camel-mcp-server on the classpath. + * By default, the MCP server is not enabled. + */ + public void setMcpEnabled(boolean mcpEnabled) { + this.mcpEnabled = mcpEnabled; + } + + public String getMcpTags() { + return mcpTags; + } + + /** + * Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are + * exposed; the untagged default pool is never exposed. When not set, no tools are exposed. + */ + public void setMcpTags(String mcpTags) { + this.mcpTags = mcpTags; + } + + public long getMcpToolTimeout() { + return mcpToolTimeout; + } + + /** + * Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the + * MCP client; the underlying route keeps running until it completes on its own. + */ + public void setMcpToolTimeout(long mcpToolTimeout) { + this.mcpToolTimeout = mcpToolTimeout; + } + + public String getMcpPath() { + return mcpPath; + } + + /** + * HTTP path where the MCP endpoint is served. + */ + public void setMcpPath(String mcpPath) { + this.mcpPath = mcpPath; + } + + public String getMcpServerName() { + return mcpServerName; + } + + /** + * MCP server name advertised to clients. Defaults to the CamelContext name. + */ + public void setMcpServerName(String mcpServerName) { + this.mcpServerName = mcpServerName; + } + /** * Whether embedded HTTP server is enabled. By default, the server is not enabled. */ @@ -506,4 +575,47 @@ public HttpServerConfigurationProperties withJwtAllowMissingIssuerAndAudience( return this; } + /** + * Whether to expose ai-tool routes as MCP tools over streamable HTTP. Requires camel-mcp-server on the classpath. + * By default, the MCP server is not enabled. + */ + public HttpServerConfigurationProperties withMcpEnabled(boolean mcpEnabled) { + this.mcpEnabled = mcpEnabled; + return this; + } + + /** + * Comma-separated list of ai-tool tags to expose as MCP tools. Only tools registered under one of these tags are + * exposed; the untagged default pool is never exposed. When not set, no tools are exposed. + */ + public HttpServerConfigurationProperties withMcpTags(String mcpTags) { + this.mcpTags = mcpTags; + return this; + } + + /** + * Per-call MCP tool execution timeout in milliseconds. A call exceeding the timeout returns an error result to the + * MCP client; the underlying route keeps running until it completes on its own. + */ + public HttpServerConfigurationProperties withMcpToolTimeout(long mcpToolTimeout) { + this.mcpToolTimeout = mcpToolTimeout; + return this; + } + + /** + * HTTP path where the MCP endpoint is served. + */ + public HttpServerConfigurationProperties withMcpPath(String mcpPath) { + this.mcpPath = mcpPath; + return this; + } + + /** + * MCP server name advertised to clients. Defaults to the CamelContext name. + */ + public HttpServerConfigurationProperties withMcpServerName(String mcpServerName) { + this.mcpServerName = mcpServerName; + return this; + } + } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainConstants.java b/core/camel-main/src/main/java/org/apache/camel/main/MainConstants.java index 0304faa0acef1..e0130140a264e 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainConstants.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainConstants.java @@ -24,6 +24,7 @@ public final class MainConstants { public static final String CLOUD_PROPERTIES_LOCATION = "camel.main.cloud-properties-location"; public static final String PROPERTY_PLACEHOLDER_LOCATION = "camel.main.property-placeholder-location"; public static final String PLATFORM_HTTP_SERVER = "platform-http-server"; + public static final String MCP_SERVER = "mcp-server"; public static final String PROFILE = "camel.main.profile"; private MainConstants() { diff --git a/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java b/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java new file mode 100644 index 0000000000000..818af0ae8ab05 --- /dev/null +++ b/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.main; + +import org.apache.camel.CamelContext; +import org.apache.camel.Service; + +/** + * Factory for creating the MCP server exposing ai-tool routes as MCP tools, for standalone Camel (not Spring Boot or + * Quarkus). Provided by camel-mcp-server. + */ +public interface McpServerFactory { + + /** + * Creates the MCP server bridge configured from the {@code camel.server.mcp-*} options. + * + * @param camelContext the camel context + * @param configuration server configuration carrying the mcp options + * @return the bridge as a {@link Service} to be managed by {@link org.apache.camel.CamelContext}. + */ + Service newMcpServer(CamelContext camelContext, HttpServerConfigurationProperties configuration); + +} From 7f64c663e7d465434d557a0abf3b400e91316cae Mon Sep 17 00:00:00 2001 From: croway Date: Mon, 3 Aug 2026 12:27:24 +0200 Subject: [PATCH 2/3] CAMEL-24311: camel-mcp-server - regen catalog docs copy and known-dependencies Co-Authored-By: Claude Fable 5 --- .../resources/org/apache/camel/catalog/docs/mcp-server.adoc | 6 +++--- .../camel-factoryfinder-known-dependencies.properties | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc index 4d438625d8756..17e3b436e191b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mcp-server.adoc @@ -10,7 +10,7 @@ *Since Camel {since}* The camel-mcp-server module exposes Camel routes registered via the -xref:components::ai-tool-component.adoc[ai-tool] component as tools of a +xref:ROOT:ai-tool-component.adoc[ai-tool] component as tools of a https://modelcontextprotocol.io[Model Context Protocol] (MCP) server, served over MCP streamable HTTP. No route is needed for the server itself: add the dependency, configure which tags to expose, and every matching `ai-tool` route @@ -125,7 +125,7 @@ camelContext.addService(new McpServerBridge(configuration)); The MCP endpoint is then served at `http://:/mcp` on the Camel main HTTP server. Any MCP client can connect over streamable HTTP, for example another Camel integration using the -xref:components::openai-component.adoc[camel-openai] MCP client: +xref:ROOT:openai-component.adoc[camel-openai] MCP client: [source,java] ---- @@ -209,7 +209,7 @@ following rules: router, so platform-http authentication (basic, JWT via `camel.server.authentication*` options) applies to it. The MCP specification's authorization model is OAuth 2.1; see - xref:components:others:oauth.adoc[camel-oauth] for resource-server style + xref:oauth.adoc[camel-oauth] for resource-server style protection. On Quarkus and Spring Boot, authentication is owned by the native runtime security. diff --git a/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties b/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties index 1ce0ccf39027e..e97aa071a6542 100644 --- a/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties +++ b/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties @@ -36,6 +36,7 @@ META-INF/services/org/apache/camel/kafka-resume-strategy=camel:kafka META-INF/services/org/apache/camel/kinesis-resume-strategy=camel:aws2-kinesis META-INF/services/org/apache/camel/lra-saga-service=camel:lra META-INF/services/org/apache/camel/mcp-server-engine=camel:mcp-server +META-INF/services/org/apache/camel/mcp-server=camel:mcp-server META-INF/services/org/apache/camel/mdc-service=camel:mdc META-INF/services/org/apache/camel/micrometer-observability-tracer=camel:micrometer-observability META-INF/services/org/apache/camel/micrometer-prometheus=camel:micrometer-prometheus From d71602c7dbf0d6378f531f2d72181c5475a6ffc1 Mon Sep 17 00:00:00 2001 From: croway Date: Mon, 3 Aug 2026 12:35:49 +0200 Subject: [PATCH 3/3] CAMEL-24311: add @since to McpServerFactory Co-Authored-By: Claude Fable 5 --- .../src/main/java/org/apache/camel/main/McpServerFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java b/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java index 818af0ae8ab05..0216483ac1578 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/McpServerFactory.java @@ -22,6 +22,8 @@ /** * Factory for creating the MCP server exposing ai-tool routes as MCP tools, for standalone Camel (not Spring Boot or * Quarkus). Provided by camel-mcp-server. + * + * @since 4.22 */ public interface McpServerFactory {