diff --git a/catalog/pom.xml b/catalog/pom.xml
index 4da6127a695..82cbaf373af 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -2827,6 +2827,19 @@
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
org.apache.camel.quarkus
camel-quarkus-mdc
diff --git a/docs/modules/ROOT/examples/others/mcp-server.yml b/docs/modules/ROOT/examples/others/mcp-server.yml
new file mode 100644
index 00000000000..596ae964bf5
--- /dev/null
+++ b/docs/modules/ROOT/examples/others/mcp-server.yml
@@ -0,0 +1,13 @@
+# Do not edit directly!
+# This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page
+cqArtifactId: camel-quarkus-mcp-server
+cqArtifactIdBase: mcp-server
+cqNativeSupported: true
+cqStatus: Stable
+cqDeprecated: false
+cqJvmSince: 3.39.0
+cqNativeSince: 3.39.0
+cqCamelPartName: mcp-server
+cqCamelPartTitle: MCP Server
+cqCamelPartDescription: Expose ai-tool routes as MCP tools over streamable HTTP
+cqExtensionPageTitle: MCP Server
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index d0cd2289b1a..e1ef9f5e511 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -226,6 +226,7 @@
*** xref:reference/extensions/log.adoc[Log]
*** xref:reference/extensions/lucene.adoc[Lucene]
*** xref:reference/extensions/lumberjack.adoc[Lumberjack]
+*** xref:reference/extensions/mcp-server.adoc[MCP Server]
*** xref:reference/extensions/mina-sftp.adoc[MINA SFTP]
*** xref:reference/extensions/mllp.adoc[MLLP]
*** xref:reference/extensions/mvel.adoc[MVEL]
diff --git a/docs/modules/ROOT/pages/reference/extensions/mcp-server.adoc b/docs/modules/ROOT/pages/reference/extensions/mcp-server.adoc
new file mode 100644
index 00000000000..96dae1aa262
--- /dev/null
+++ b/docs/modules/ROOT/pages/reference/extensions/mcp-server.adoc
@@ -0,0 +1,301 @@
+// Do not edit directly!
+// This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page
+[id="extensions-mcp-server"]
+= MCP Server
+:linkattrs:
+:cq-artifact-id: camel-quarkus-mcp-server
+:cq-native-supported: true
+:cq-status: Stable
+:cq-status-deprecation: Stable
+:cq-description: Expose ai-tool routes as MCP tools over streamable HTTP
+:cq-deprecated: false
+:cq-jvm-since: 3.39.0
+:cq-native-since: 3.39.0
+
+ifeval::[{doc-show-badges} == true]
+[.badges]
+[.badge-key]##JVM since##[.badge-supported]##3.39.0## [.badge-key]##Native since##[.badge-supported]##3.39.0##
+endif::[]
+
+The MCP Server extension exposes Camel routes registered via the `ai-tool`
+component as tools of a Model Context Protocol (MCP) server, served through
+the quarkiverse https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html[quarkus-mcp-server]
+extension. No route is needed for the server itself: add the extension, tag
+the `ai-tool` routes to expose, and any MCP client (another Camel
+application, an IDE, a coding agent) can discover and call them.
+
+Tool semantics — tag-based opt-in (the untagged default pool is never
+exposed), flat-namespace collision refusal, per-call timeout and error
+sanitization — are owned by the runtime-agnostic `camel-mcp-server-api`
+bridge and are identical on every Camel runtime. Serving concerns (endpoint
+path, transports, authentication) are owned by quarkus-mcp-server and
+configured via `quarkus.mcp.server.*`.
+
+
+[id="extensions-mcp-server-whats-inside"]
+== What's inside
+
+* xref:{cq-camel-components}:others:mcp-server.adoc[MCP Server]
+
+Please refer to the above link for usage and configuration details.
+
+[id="extensions-mcp-server-maven-coordinates"]
+== Maven coordinates
+
+https://{link-quarkus-code-generator}/?extension-search=camel-quarkus-mcp-server[Create a new project with this extension on {link-quarkus-code-generator}, window="_blank"]
+
+Or add the coordinates to your existing project:
+
+[source,xml]
+----
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+
+----
+ifeval::[{doc-show-user-guide-link} == true]
+Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
+endif::[]
+
+[id="extensions-mcp-server-usage"]
+== Usage
+Define tools as regular `ai-tool` routes and give them tags:
+
+[source,java]
+----
+from("ai-tool:query_db?tags=crm&description=Query customer database"
+ + "¶meter.customerId=string¶meter.customerId.required=true")
+ .to("jdbc:dataSource");
+----
+
+or in YAML DSL:
+
+[source,yaml]
+----
+- route:
+ from:
+ uri: "ai-tool:send_email"
+ parameters:
+ description: "Send email notification"
+ tags: "notify"
+ parameter.to: string
+ parameter.to.description: "Recipient address"
+ parameter.to.required: "true"
+ parameter.subject: string
+ parameter.priority: string
+ parameter.priority.enum: "low,normal,high"
+ steps:
+ - to: "smtp://mail.example.com"
+----
+
+Tool parameters are declared with the `parameter.NAME` options: the value is
+the JSON type (`string`, `integer`, `number`, `boolean`), and the
+`parameter.NAME.description`, `parameter.NAME.required` and
+`parameter.NAME.enum` options refine the generated input schema. Arguments
+arrive as message headers in the route (`${header.customerId}`).
+
+[id="extensions-mcp-server-usage-combining-the-camel-and-quarkus-mcp-server-configuration"]
+=== Combining the Camel and quarkus-mcp-server configuration
+
+The configuration is split by ownership: `quarkus.camel.mcp-server.*`
+decides *which tools exist and how they execute* (identical semantics on
+every Camel runtime), while `quarkus.mcp.server.*` decides *how the server
+is exposed*. Both are set side by side in `application.properties`:
+
+[source,properties]
+----
+# Camel-owned: tool selection and execution
+quarkus.camel.mcp-server.tags = crm,notify
+quarkus.camel.mcp-server.tool-timeout = 10000
+
+# quarkus-mcp-server-owned: serving and server identity
+quarkus.mcp.server.server-info.name = my-integration-app
+quarkus.mcp.server.server-info.version = 1.0.0
+quarkus.mcp.server.http.root-path = /mcp
+
+# quarkus-mcp-server-owned: diagnostics while developing
+quarkus.mcp.server.traffic-logging.enabled = true
+quarkus.mcp.server.traffic-logging.text-limit = 200
+----
+
+See the
+https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html#configuration-reference[quarkus-mcp-server configuration reference]
+for the full list of `quarkus.mcp.server.*` properties (transports, dev UI,
+authentication, guardrails, pagination, timeouts).
+
+[id="extensions-mcp-server-usage-connecting-mcp-clients"]
+=== Connecting MCP clients
+
+Any MCP client can connect over streamable HTTP. Another Camel integration
+can consume the tools with the camel-openai MCP client and automatic tool
+execution:
+
+[source,java]
+----
+from("direct:agent")
+ .to("openai:chat-completion"
+ + "?model={{llm.model}}"
+ + "&autoToolExecution=true"
+ + "&mcpServer.myCamelTools.transportType=streamableHttp"
+ + "&mcpServer.myCamelTools.url=http://localhost:8080/mcp");
+----
+
+A coding agent or IDE is configured with the same URL, e.g. in an
+`mcp.json`-style client configuration:
+
+[source,json]
+----
+{
+ "mcpServers": {
+ "my-integration-app": {
+ "type": "http",
+ "url": "http://localhost:8080/mcp"
+ }
+ }
+}
+----
+
+[id="extensions-mcp-server-usage-serving-over-stdio"]
+=== Serving over stdio
+
+Out of the box the tools are served over streamable HTTP. MCP clients that
+launch the server as a subprocess speak over stdin/stdout instead — add the
+stdio transport alongside the extension:
+
+[source,xml]
+----
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-stdio
+
+ 1.13.1
+
+----
+
+The stdio transport is active as soon as it is on the classpath
+(`quarkus.mcp.server.stdio.enabled` defaults to `true`), and the same Camel
+tools are then served over both transports. To serve over stdio *only*,
+disable the HTTP transport of the default MCP server:
+
+[source,properties]
+----
+quarkus.mcp.server."".http.enabled = false
+----
+
+The server name of the default MCP server is the literal string
+``, so it has to be quoted. Conversely,
+`quarkus.mcp.server.stdio.enabled = false` keeps the stdio extension on the
+classpath without activating it, which is useful when the same application
+is deployed both ways.
+
+Because stdout carries the MCP protocol, nothing else may be written to it;
+`quarkus-mcp-server-stdio` takes care of this by routing console logging to
+stderr. A stdio server is typically built as a native executable and
+launched by the client:
+
+[source,json]
+----
+{
+ "mcpServers": {
+ "my-integration-app": {
+ "command": "/path/to/target/my-integration-app-1.0.0-runner"
+ }
+ }
+}
+----
+
+See the
+https://docs.quarkiverse.io/quarkus-mcp-server/dev/getting-started-stdio.html[quarkus-mcp-server stdio guide]
+and the
+https://docs.quarkiverse.io/quarkus-mcp-server/dev/reference-configuration.html[configuration reference]
+for the transport options. On Spring Boot the equivalent setup is described
+in the
+https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html[Spring AI MCP Server Boot Starter documentation].
+
+[id="extensions-mcp-server-usage-mixing-with-quarkus-mcp-server-annotated-tools"]
+=== Mixing with quarkus-mcp-server annotated tools
+
+Camel tools coexist with tools defined natively with quarkus-mcp-server —
+both are served by the same MCP server and appear in the same `tools/list`.
+For example, a `@Tool`-annotated business method:
+
+[source,java]
+----
+public class CalculatorTools {
+
+ @Tool(name = "add_numbers", description = "Add two numbers")
+ String add(
+ @ToolArg(description = "First addend") long a,
+ @ToolArg(description = "Second addend") long b) {
+ return String.valueOf(a + b);
+ }
+}
+----
+
+is exposed alongside the `ai-tool` routes. Annotated tools are registered at
+build time; Camel tools are added and removed dynamically with the route
+lifecycle. Choose distinct tool names — MCP has a flat tool namespace.
+
+[id="extensions-mcp-server-usage-dynamic-tools"]
+=== Dynamic tools
+
+The exposed tool list follows the route lifecycle: stopping or suspending an
+`ai-tool` route removes its tool, starting or resuming it publishes the tool
+again, and connected clients are notified via
+`notifications/tools/list_changed`:
+
+[source,java]
+----
+camelContext.getRouteController().stopRoute("query-db-route"); // tool disappears
+camelContext.getRouteController().startRoute("query-db-route"); // tool is back
+----
+
+[id="extensions-mcp-server-usage-error-handling"]
+=== Error handling
+
+Results returned to MCP clients are sanitized by the bridge: a route
+exception produces an error result with the generic message
+`Tool execution failed` (the cause is logged server-side and never sent to
+the client), a missing or invalid argument returns the validation message,
+and a call exceeding `quarkus.camel.mcp-server.tool-timeout` returns
+`Tool execution timed out` while the route keeps running until it completes
+on its own.
+
+On Camel Main and Camel JBang the equivalent setup is the
+`camel-mcp-server` module with the `camel.server.mcp-*` options; on Spring
+Boot it is the `camel-mcp-server-starter`.
+
+
+[id="extensions-mcp-server-additional-camel-quarkus-configuration"]
+== Additional Camel Quarkus configuration
+
+[width="100%",cols="80,5,15",options="header"]
+|===
+| Configuration property | Type | Default
+
+
+a|icon:lock[title=Fixed at build time] [[quarkus-camel-mcp-server-enabled]]`link:#quarkus-camel-mcp-server-enabled[quarkus.camel.mcp-server.enabled]`
+
+Whether to expose ai-tool routes as MCP tools through the quarkus-mcp-server extension.
+| `boolean`
+| `true`
+
+a|icon:lock[title=Fixed at build time] [[quarkus-camel-mcp-server-tags]]`link:#quarkus-camel-mcp-server-tags[quarkus.camel.mcp-server.tags]`
+
+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`
+|
+
+a|icon:lock[title=Fixed at build time] [[quarkus-camel-mcp-server-tool-timeout]]`link:#quarkus-camel-mcp-server-tool-timeout[quarkus.camel.mcp-server.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.
+| `long`
+| `20000`
+|===
+
+[.configuration-legend]
+{doc-link-icon-lock}[title=Fixed at build time] Configuration property fixed at build time. All other configuration properties are overridable at runtime.
+
diff --git a/extensions/mcp-server/deployment/pom.xml b/extensions/mcp-server/deployment/pom.xml
new file mode 100644
index 00000000000..908eb319600
--- /dev/null
+++ b/extensions/mcp-server/deployment/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+ 4.0.0
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-parent
+ 3.39.0-SNAPSHOT
+ ../pom.xml
+
+
+ camel-quarkus-mcp-server-deployment
+ Camel Quarkus :: MCP Server :: Deployment
+
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-core-deployment
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http-deployment
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
+
+
diff --git a/extensions/mcp-server/deployment/src/main/java/org/apache/camel/quarkus/component/mcp/server/deployment/McpServerProcessor.java b/extensions/mcp-server/deployment/src/main/java/org/apache/camel/quarkus/component/mcp/server/deployment/McpServerProcessor.java
new file mode 100644
index 00000000000..f43febbb354
--- /dev/null
+++ b/extensions/mcp-server/deployment/src/main/java/org/apache/camel/quarkus/component/mcp/server/deployment/McpServerProcessor.java
@@ -0,0 +1,53 @@
+/*
+ * 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.quarkus.component.mcp.server.deployment;
+
+import java.util.function.BooleanSupplier;
+
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import org.apache.camel.quarkus.component.mcp.server.CamelMcpServerConfig;
+import org.apache.camel.quarkus.component.mcp.server.CamelMcpServerRecorder;
+import org.apache.camel.quarkus.core.deployment.spi.RuntimeCamelContextCustomizerBuildItem;
+
+class McpServerProcessor {
+
+ private static final String FEATURE = "camel-mcp-server";
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+
+ @BuildStep(onlyIf = McpServerEnabled.class)
+ @Record(ExecutionTime.RUNTIME_INIT)
+ RuntimeCamelContextCustomizerBuildItem mcpServerBridge(CamelMcpServerRecorder recorder, CamelMcpServerConfig config) {
+ return new RuntimeCamelContextCustomizerBuildItem(
+ recorder.createContextCustomizer(config.tags().orElse(null), config.toolTimeout()));
+ }
+
+ static final class McpServerEnabled implements BooleanSupplier {
+ CamelMcpServerConfig config;
+
+ @Override
+ public boolean getAsBoolean() {
+ return config.enabled();
+ }
+ }
+}
diff --git a/extensions/mcp-server/pom.xml b/extensions/mcp-server/pom.xml
new file mode 100644
index 00000000000..f514871de76
--- /dev/null
+++ b/extensions/mcp-server/pom.xml
@@ -0,0 +1,39 @@
+
+
+
+ 4.0.0
+
+ org.apache.camel.quarkus
+ camel-quarkus-extensions
+ 3.39.0-SNAPSHOT
+ ../pom.xml
+
+
+ camel-quarkus-mcp-server-parent
+ Camel Quarkus :: MCP Server
+ pom
+
+
+ deployment
+ runtime
+
+
diff --git a/extensions/mcp-server/runtime/pom.xml b/extensions/mcp-server/runtime/pom.xml
new file mode 100644
index 00000000000..63af0d6f595
--- /dev/null
+++ b/extensions/mcp-server/runtime/pom.xml
@@ -0,0 +1,105 @@
+
+
+
+ 4.0.0
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-parent
+ 3.39.0-SNAPSHOT
+ ../pom.xml
+
+
+ camel-quarkus-mcp-server
+ Camel Quarkus :: MCP Server :: Runtime
+ Expose ai-tool routes as MCP tools over streamable HTTP
+
+
+ 3.39.0
+ 3.39.0
+
+
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-core
+
+
+ org.apache.camel
+ camel-mcp-server-api
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http
+
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
+
+
+
+
+ full
+
+
+ !quickly
+
+
+
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-maven-plugin
+
+
+ update-extension-doc-page
+
+ update-extension-doc-page
+
+ process-classes
+
+
+
+
+
+
+
+
diff --git a/extensions/mcp-server/runtime/src/main/doc/intro.adoc b/extensions/mcp-server/runtime/src/main/doc/intro.adoc
new file mode 100644
index 00000000000..7c60ee6f2d4
--- /dev/null
+++ b/extensions/mcp-server/runtime/src/main/doc/intro.adoc
@@ -0,0 +1,13 @@
+The MCP Server extension exposes Camel routes registered via the `ai-tool`
+component as tools of a Model Context Protocol (MCP) server, served through
+the quarkiverse https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html[quarkus-mcp-server]
+extension. No route is needed for the server itself: add the extension, tag
+the `ai-tool` routes to expose, and any MCP client (another Camel
+application, an IDE, a coding agent) can discover and call them.
+
+Tool semantics — tag-based opt-in (the untagged default pool is never
+exposed), flat-namespace collision refusal, per-call timeout and error
+sanitization — are owned by the runtime-agnostic `camel-mcp-server-api`
+bridge and are identical on every Camel runtime. Serving concerns (endpoint
+path, transports, authentication) are owned by quarkus-mcp-server and
+configured via `quarkus.mcp.server.*`.
diff --git a/extensions/mcp-server/runtime/src/main/doc/usage.adoc b/extensions/mcp-server/runtime/src/main/doc/usage.adoc
new file mode 100644
index 00000000000..595f81e6ed5
--- /dev/null
+++ b/extensions/mcp-server/runtime/src/main/doc/usage.adoc
@@ -0,0 +1,200 @@
+Define tools as regular `ai-tool` routes and give them tags:
+
+[source,java]
+----
+from("ai-tool:query_db?tags=crm&description=Query customer database"
+ + "¶meter.customerId=string¶meter.customerId.required=true")
+ .to("jdbc:dataSource");
+----
+
+or in YAML DSL:
+
+[source,yaml]
+----
+- route:
+ from:
+ uri: "ai-tool:send_email"
+ parameters:
+ description: "Send email notification"
+ tags: "notify"
+ parameter.to: string
+ parameter.to.description: "Recipient address"
+ parameter.to.required: "true"
+ parameter.subject: string
+ parameter.priority: string
+ parameter.priority.enum: "low,normal,high"
+ steps:
+ - to: "smtp://mail.example.com"
+----
+
+Tool parameters are declared with the `parameter.NAME` options: the value is
+the JSON type (`string`, `integer`, `number`, `boolean`), and the
+`parameter.NAME.description`, `parameter.NAME.required` and
+`parameter.NAME.enum` options refine the generated input schema. Arguments
+arrive as message headers in the route (`${header.customerId}`).
+
+=== Combining the Camel and quarkus-mcp-server configuration
+
+The configuration is split by ownership: `quarkus.camel.mcp-server.*`
+decides *which tools exist and how they execute* (identical semantics on
+every Camel runtime), while `quarkus.mcp.server.*` decides *how the server
+is exposed*. Both are set side by side in `application.properties`:
+
+[source,properties]
+----
+# Camel-owned: tool selection and execution
+quarkus.camel.mcp-server.tags = crm,notify
+quarkus.camel.mcp-server.tool-timeout = 10000
+
+# quarkus-mcp-server-owned: serving and server identity
+quarkus.mcp.server.server-info.name = my-integration-app
+quarkus.mcp.server.server-info.version = 1.0.0
+quarkus.mcp.server.http.root-path = /mcp
+
+# quarkus-mcp-server-owned: diagnostics while developing
+quarkus.mcp.server.traffic-logging.enabled = true
+quarkus.mcp.server.traffic-logging.text-limit = 200
+----
+
+See the
+https://docs.quarkiverse.io/quarkus-mcp-server/dev/index.html#configuration-reference[quarkus-mcp-server configuration reference]
+for the full list of `quarkus.mcp.server.*` properties (transports, dev UI,
+authentication, guardrails, pagination, timeouts).
+
+=== Connecting MCP clients
+
+Any MCP client can connect over streamable HTTP. Another Camel integration
+can consume the tools with the camel-openai MCP client and automatic tool
+execution:
+
+[source,java]
+----
+from("direct:agent")
+ .to("openai:chat-completion"
+ + "?model={{llm.model}}"
+ + "&autoToolExecution=true"
+ + "&mcpServer.myCamelTools.transportType=streamableHttp"
+ + "&mcpServer.myCamelTools.url=http://localhost:8080/mcp");
+----
+
+A coding agent or IDE is configured with the same URL, e.g. in an
+`mcp.json`-style client configuration:
+
+[source,json]
+----
+{
+ "mcpServers": {
+ "my-integration-app": {
+ "type": "http",
+ "url": "http://localhost:8080/mcp"
+ }
+ }
+}
+----
+
+=== Serving over stdio
+
+Out of the box the tools are served over streamable HTTP. MCP clients that
+launch the server as a subprocess speak over stdin/stdout instead — add the
+stdio transport alongside the extension:
+
+[source,xml]
+----
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-stdio
+
+ 1.13.1
+
+----
+
+The stdio transport is active as soon as it is on the classpath
+(`quarkus.mcp.server.stdio.enabled` defaults to `true`), and the same Camel
+tools are then served over both transports. To serve over stdio *only*,
+disable the HTTP transport of the default MCP server:
+
+[source,properties]
+----
+quarkus.mcp.server."".http.enabled = false
+----
+
+The server name of the default MCP server is the literal string
+``, so it has to be quoted. Conversely,
+`quarkus.mcp.server.stdio.enabled = false` keeps the stdio extension on the
+classpath without activating it, which is useful when the same application
+is deployed both ways.
+
+Because stdout carries the MCP protocol, nothing else may be written to it;
+`quarkus-mcp-server-stdio` takes care of this by routing console logging to
+stderr. A stdio server is typically built as a native executable and
+launched by the client:
+
+[source,json]
+----
+{
+ "mcpServers": {
+ "my-integration-app": {
+ "command": "/path/to/target/my-integration-app-1.0.0-runner"
+ }
+ }
+}
+----
+
+See the
+https://docs.quarkiverse.io/quarkus-mcp-server/dev/getting-started-stdio.html[quarkus-mcp-server stdio guide]
+and the
+https://docs.quarkiverse.io/quarkus-mcp-server/dev/reference-configuration.html[configuration reference]
+for the transport options. On Spring Boot the equivalent setup is described
+in the
+https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html[Spring AI MCP Server Boot Starter documentation].
+
+=== Mixing with quarkus-mcp-server annotated tools
+
+Camel tools coexist with tools defined natively with quarkus-mcp-server —
+both are served by the same MCP server and appear in the same `tools/list`.
+For example, a `@Tool`-annotated business method:
+
+[source,java]
+----
+public class CalculatorTools {
+
+ @Tool(name = "add_numbers", description = "Add two numbers")
+ String add(
+ @ToolArg(description = "First addend") long a,
+ @ToolArg(description = "Second addend") long b) {
+ return String.valueOf(a + b);
+ }
+}
+----
+
+is exposed alongside the `ai-tool` routes. Annotated tools are registered at
+build time; Camel tools are added and removed dynamically with the route
+lifecycle. Choose distinct tool names — MCP has a flat tool namespace.
+
+=== Dynamic tools
+
+The exposed tool list follows the route lifecycle: stopping or suspending an
+`ai-tool` route removes its tool, starting or resuming it publishes the tool
+again, and connected clients are notified via
+`notifications/tools/list_changed`:
+
+[source,java]
+----
+camelContext.getRouteController().stopRoute("query-db-route"); // tool disappears
+camelContext.getRouteController().startRoute("query-db-route"); // tool is back
+----
+
+=== Error handling
+
+Results returned to MCP clients are sanitized by the bridge: a route
+exception produces an error result with the generic message
+`Tool execution failed` (the cause is logged server-side and never sent to
+the client), a missing or invalid argument returns the validation message,
+and a call exceeding `quarkus.camel.mcp-server.tool-timeout` returns
+`Tool execution timed out` while the route keeps running until it completes
+on its own.
+
+On Camel Main and Camel JBang the equivalent setup is the
+`camel-mcp-server` module with the `camel.server.mcp-*` options; on Spring
+Boot it is the `camel-mcp-server-starter`.
diff --git a/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/CamelMcpServerConfig.java b/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/CamelMcpServerConfig.java
new file mode 100644
index 00000000000..5975fc5d86f
--- /dev/null
+++ b/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/CamelMcpServerConfig.java
@@ -0,0 +1,58 @@
+/*
+ * 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.quarkus.component.mcp.server;
+
+import java.util.Optional;
+
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
+
+/**
+ * Bridge-owned configuration of the Camel MCP server. Serving concerns (endpoint path, transports, authentication)
+ * are owned by the quarkiverse quarkus-mcp-server extension and configured via {@code quarkus.mcp.server.*}.
+ */
+@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+@ConfigMapping(prefix = "quarkus.camel.mcp-server")
+public interface CamelMcpServerConfig {
+
+ /**
+ * Whether to expose ai-tool routes as MCP tools through the quarkus-mcp-server extension.
+ *
+ * @asciidoclet
+ */
+ @WithDefault("true")
+ boolean enabled();
+
+ /**
+ * 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.
+ *
+ * @asciidoclet
+ */
+ Optional tags();
+
+ /**
+ * 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.
+ *
+ * @asciidoclet
+ */
+ @WithDefault("20000")
+ long toolTimeout();
+}
diff --git a/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/CamelMcpServerRecorder.java b/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/CamelMcpServerRecorder.java
new file mode 100644
index 00000000000..f7d5ef2157c
--- /dev/null
+++ b/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/CamelMcpServerRecorder.java
@@ -0,0 +1,54 @@
+/*
+ * 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.quarkus.component.mcp.server;
+
+import io.quarkiverse.mcp.server.ToolManager;
+import io.quarkus.arc.Arc;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.annotations.Recorder;
+import org.apache.camel.CamelContext;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.mcp.server.McpServerBridge;
+import org.apache.camel.component.mcp.server.McpServerConfiguration;
+import org.apache.camel.component.mcp.server.McpServerEngine;
+import org.apache.camel.spi.CamelContextCustomizer;
+
+@Recorder
+public class CamelMcpServerRecorder {
+
+ public RuntimeValue createContextCustomizer(String tags, long toolTimeout) {
+ return new RuntimeValue<>(new CamelContextCustomizer() {
+ @Override
+ public void configure(CamelContext camelContext) {
+ ToolManager toolManager = Arc.container().instance(ToolManager.class).get();
+ QuarkusMcpServerEngine engine = new QuarkusMcpServerEngine(toolManager);
+
+ McpServerConfiguration configuration = new McpServerConfiguration();
+ configuration.setTags(tags);
+ configuration.setToolTimeout(toolTimeout);
+ McpServerBridge bridge = new McpServerBridge(configuration);
+ try {
+ // the bridge resolves the engine registry-first
+ camelContext.getRegistry().bind("quarkusCamelMcpServerEngine", McpServerEngine.class, engine);
+ camelContext.addService(bridge);
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeCamelException(e);
+ }
+ }
+ });
+ }
+}
diff --git a/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/QuarkusMcpServerEngine.java b/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/QuarkusMcpServerEngine.java
new file mode 100644
index 00000000000..2f9a19f6be0
--- /dev/null
+++ b/extensions/mcp-server/runtime/src/main/java/org/apache/camel/quarkus/component/mcp/server/QuarkusMcpServerEngine.java
@@ -0,0 +1,104 @@
+/*
+ * 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.quarkus.component.mcp.server;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import io.quarkiverse.mcp.server.TextContent;
+import io.quarkiverse.mcp.server.ToolManager;
+import io.quarkiverse.mcp.server.ToolResponse;
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.ai.tool.AiToolParameterHelper.ParameterDef;
+import org.apache.camel.component.mcp.server.McpServerEngine;
+import org.apache.camel.component.mcp.server.McpServerInfo;
+import org.apache.camel.component.mcp.server.McpServerTool;
+import org.apache.camel.component.mcp.server.McpToolCallResult;
+import org.apache.camel.support.service.ServiceSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link McpServerEngine} publishing tools into the quarkiverse quarkus-mcp-server through its programmatic
+ * {@link ToolManager} API. Serving concerns (endpoint path, transports, authentication) are owned by
+ * quarkus-mcp-server and configured via {@code quarkus.mcp.server.*}.
+ */
+public class QuarkusMcpServerEngine extends ServiceSupport implements McpServerEngine {
+
+ private static final Logger LOG = LoggerFactory.getLogger(QuarkusMcpServerEngine.class);
+
+ private final ToolManager toolManager;
+ private CamelContext camelContext;
+
+ public QuarkusMcpServerEngine(ToolManager toolManager) {
+ this.toolManager = toolManager;
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public void initialize(McpServerInfo info) {
+ // serving identity is owned by quarkus.mcp.server.*; the hint is deliberately ignored
+ LOG.debug("quarkus-mcp-server engine initialized; serving configuration is owned by quarkus.mcp.server.*");
+ }
+
+ @Override
+ public void toolAdded(McpServerTool tool) {
+ ToolManager.ToolDefinition definition = toolManager.newTool(tool.name())
+ .setDescription(tool.description());
+ for (Map.Entry parameter : tool.parameters().entrySet()) {
+ ParameterDef def = parameter.getValue();
+ definition.addArgument(parameter.getKey(), def.getDescription(), def.isRequired(), javaType(def));
+ }
+ definition.setHandler(arguments -> {
+ Map args = arguments.args() != null ? arguments.args() : Map.of();
+ McpToolCallResult result = tool.handler().call(args);
+ return result.isError()
+ ? ToolResponse.error(result.text()) : ToolResponse.success(new TextContent(result.text()));
+ });
+ definition.register();
+ LOG.debug("MCP tool added: {}", tool.name());
+ }
+
+ @Override
+ public void toolRemoved(String toolName) {
+ try {
+ toolManager.removeTool(toolName);
+ LOG.debug("MCP tool removed: {}", toolName);
+ } catch (Exception e) {
+ LOG.debug("Failed to remove MCP tool {}: {}", toolName, e.getMessage());
+ }
+ }
+
+ private static Type javaType(ParameterDef def) {
+ String type = def.getType() != null ? def.getType() : "string";
+ return switch (type) {
+ case "integer", "int", "long" -> Long.class;
+ case "number", "double", "float" -> Double.class;
+ case "boolean", "bool" -> Boolean.class;
+ default -> String.class;
+ };
+ }
+}
diff --git a/extensions/mcp-server/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/mcp-server/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 00000000000..875f203389b
--- /dev/null
+++ b/extensions/mcp-server/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+# This is a generated file. Do not edit directly!
+# To re-generate, run the following command from the top level directory:
+#
+# mvn -N cq:update-quarkus-metadata
+#
+---
+name: "Camel MCP Server"
+description: "Expose ai-tool routes as MCP tools over streamable HTTP"
+metadata:
+ icon-url: "https://raw.githubusercontent.com/apache/camel-website/main/antora-ui-camel/src/img/logo-d.svg"
+ sponsor: "Apache Software Foundation"
+ guide: "https://camel.apache.org/camel-quarkus/latest/reference/extensions/mcp-server.html"
+ categories:
+ - "integration"
+ config:
+ - "quarkus.camel.mcp-server"
+ status: "stable"
+ integrates:
+ - name: "Camel"
+ artifact: "org.apache.camel:camel-base"
+ version: "${camel.version}"
\ No newline at end of file
diff --git a/extensions/pom.xml b/extensions/pom.xml
index bfb301db5eb..023c0b2bca7 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -204,6 +204,7 @@
management
mapstruct
master
+ mcp-server
mdc
micrometer
micrometer-observability
diff --git a/integration-tests/mcp-server/pom.xml b/integration-tests/mcp-server/pom.xml
new file mode 100644
index 00000000000..b6accb9bdfd
--- /dev/null
+++ b/integration-tests/mcp-server/pom.xml
@@ -0,0 +1,146 @@
+
+
+
+ 4.0.0
+
+ org.apache.camel.quarkus
+ camel-quarkus-build-parent-it
+ 3.39.0-SNAPSHOT
+ ../../poms/build-parent-it/pom.xml
+
+
+ camel-quarkus-integration-test-mcp-server
+ Camel Quarkus :: Integration Tests :: MCP Server
+ Integration tests for Camel Quarkus MCP Server extension
+
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-ai-tool
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-test
+ test
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ io.quarkus
+ quarkus-junit
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+ ${rerun.failing.test.count}
+
+
+
+
+
+
+
+
+ virtualDependencies
+
+
+ !noVirtualDependencies
+
+
+
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-ai-tool-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+
diff --git a/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerResource.java b/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerResource.java
new file mode 100644
index 00000000000..1012e173fad
--- /dev/null
+++ b/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerResource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.quarkus.component.mcp.server.it;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.apache.camel.CamelContext;
+
+@Path("/mcp-server")
+public class McpServerResource {
+
+ @Inject
+ CamelContext camelContext;
+
+ @Path("/route/{routeId}/start")
+ @POST
+ public void startRoute(@PathParam("routeId") String routeId) throws Exception {
+ camelContext.getRouteController().startRoute(routeId);
+ }
+
+ @Path("/route/{routeId}/stop")
+ @POST
+ public void stopRoute(@PathParam("routeId") String routeId) throws Exception {
+ camelContext.getRouteController().stopRoute(routeId);
+ }
+
+ @Path("/route/{routeId}/status")
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String routeStatus(@PathParam("routeId") String routeId) {
+ return camelContext.getRouteController().getRouteStatus(routeId).name();
+ }
+}
diff --git a/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerRoutes.java b/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerRoutes.java
new file mode 100644
index 00000000000..ab1efba1681
--- /dev/null
+++ b/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerRoutes.java
@@ -0,0 +1,47 @@
+/*
+ * 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.quarkus.component.mcp.server.it;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.builder.RouteBuilder;
+
+@ApplicationScoped
+public class McpServerRoutes extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("ai-tool:say_hello?tags=conformance&description=Say hello"
+ + "¶meter.name=string¶meter.name.description=Who to greet¶meter.name.required=true")
+ .routeId("say-hello-route")
+ .setBody(simple("Hello ${header.name}"));
+
+ from("ai-tool:fail_tool?tags=conformance&description=Always fails")
+ .process(e -> {
+ throw new IllegalStateException("secret internal detail");
+ });
+
+ from("ai-tool:slow_tool?tags=conformance&description=Exceeds the tool timeout")
+ .delay(6000)
+ .setBody(constant("done"));
+
+ from("ai-tool:hidden_tool?description=Untagged tool, must not be exposed")
+ .setBody(constant("hidden"));
+
+ from("ai-tool:other_tool?tags=untrusted&description=Not a selected tag, must not be exposed")
+ .setBody(constant("other"));
+ }
+}
diff --git a/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/QuarkusDefinedTools.java b/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/QuarkusDefinedTools.java
new file mode 100644
index 00000000000..88a6cf149fb
--- /dev/null
+++ b/integration-tests/mcp-server/src/main/java/org/apache/camel/quarkus/component/mcp/server/it/QuarkusDefinedTools.java
@@ -0,0 +1,33 @@
+/*
+ * 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.quarkus.component.mcp.server.it;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+
+/**
+ * A plain quarkus-mcp-server annotated tool living on the same MCP server as the Camel ai-tool routes.
+ */
+public class QuarkusDefinedTools {
+
+ @Tool(name = "add_numbers", description = "Add two numbers")
+ String add(
+ @ToolArg(description = "First addend") long a,
+ @ToolArg(description = "Second addend") long b) {
+ return String.valueOf(a + b);
+ }
+}
diff --git a/integration-tests/mcp-server/src/main/resources/application.properties b/integration-tests/mcp-server/src/main/resources/application.properties
new file mode 100644
index 00000000000..91728bf6438
--- /dev/null
+++ b/integration-tests/mcp-server/src/main/resources/application.properties
@@ -0,0 +1,18 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+quarkus.camel.mcp-server.tags=conformance
+quarkus.camel.mcp-server.tool-timeout=2000
diff --git a/integration-tests/mcp-server/src/test/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerIT.java b/integration-tests/mcp-server/src/test/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerIT.java
new file mode 100644
index 00000000000..2775bf31837
--- /dev/null
+++ b/integration-tests/mcp-server/src/test/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerIT.java
@@ -0,0 +1,24 @@
+/*
+ * 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.quarkus.component.mcp.server.it;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class McpServerIT extends McpServerTest {
+
+}
diff --git a/integration-tests/mcp-server/src/test/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerTest.java b/integration-tests/mcp-server/src/test/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerTest.java
new file mode 100644
index 00000000000..efa0e8945ae
--- /dev/null
+++ b/integration-tests/mcp-server/src/test/java/org/apache/camel/quarkus/component/mcp/server/it/McpServerTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.quarkus.component.mcp.server.it;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import io.quarkiverse.mcp.server.TextContent;
+import io.quarkiverse.mcp.server.ToolResponse;
+import io.quarkiverse.mcp.server.test.McpAssured;
+import io.quarkiverse.mcp.server.test.McpAssured.McpStreamableTestClient;
+import io.quarkiverse.mcp.server.test.McpAssured.ToolInfo;
+import io.quarkiverse.mcp.server.test.McpAssured.ToolsPage;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+/**
+ * The engine conformance scenarios (CAMEL-24313) against the quarkus-mcp-server engine, driven by McpAssured over
+ * streamable HTTP. Mirrors {@code McpServerConformanceTestSupport} from camel-mcp-server-api, which cannot be reused
+ * as-is here because it manages its own CamelContext outside Quarkus.
+ */
+@QuarkusTest
+class McpServerTest {
+
+ private McpStreamableTestClient client;
+
+ private McpStreamableTestClient client() {
+ if (client == null) {
+ client = McpAssured.newStreamableClient()
+ .setBaseUri(URI.create("http://localhost:%d".formatted(
+ ConfigProvider.getConfig().getValue("quarkus.http.test-port", Integer.class))))
+ .build()
+ .connect();
+ }
+ return client;
+ }
+
+ @AfterEach
+ void closeClient() {
+ if (client != null) {
+ client.disconnect();
+ client = null;
+ }
+ }
+
+ @Test
+ void testListToolsExposesOnlySelectedTags() {
+ client().when()
+ .toolsList(page -> assertThat(toolNames(page))
+ .contains("say_hello", "fail_tool", "slow_tool")
+ .doesNotContain("hidden_tool", "other_tool"))
+ .thenAssertResults();
+ }
+
+ @Test
+ void testQuarkusAnnotatedToolsCoexistWithCamelTools() {
+ // both tool sources are served by the same MCP server
+ client().when()
+ .toolsList(page -> assertThat(toolNames(page)).contains("add_numbers", "say_hello"))
+ .toolsCall("add_numbers", Map.of("a", 17, "b", 25), response -> {
+ assertThat(response.isError()).isNotEqualTo(Boolean.TRUE);
+ assertThat(textOf(response)).isEqualTo("42");
+ })
+ .thenAssertResults();
+ }
+
+ @Test
+ void testCallToolSuccess() {
+ client().when()
+ .toolsCall("say_hello", Map.of("name", "World"), response -> {
+ assertThat(response.isError()).isNotEqualTo(Boolean.TRUE);
+ assertThat(textOf(response)).isEqualTo("Hello World");
+ })
+ .thenAssertResults();
+ }
+
+ @Test
+ void testCallToolExecutionErrorIsSanitized() {
+ client().when()
+ .toolsCall("fail_tool", response -> {
+ assertThat(response.isError()).isTrue();
+ assertThat(textOf(response))
+ .doesNotContain("secret internal detail")
+ .isEqualTo("Tool execution failed");
+ })
+ .thenAssertResults();
+ }
+
+ @Test
+ void testCallToolTimeout() {
+ client().when()
+ .toolsCall("slow_tool", response -> {
+ assertThat(response.isError()).isTrue();
+ assertThat(textOf(response)).contains("timed out");
+ })
+ .thenAssertResults();
+ }
+
+ @Test
+ void testToolsListReflectsRouteStopAndStart() {
+ client().when()
+ .toolsList(page -> assertThat(toolNames(page)).contains("say_hello"))
+ .thenAssertResults();
+
+ controlRoute("stop");
+ assertThat(routeStatus()).isEqualTo("Stopped");
+ await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> client().when()
+ .toolsList(page -> assertThat(toolNames(page)).doesNotContain("say_hello"))
+ .thenAssertResults());
+
+ controlRoute("start");
+ assertThat(routeStatus()).isEqualTo("Started");
+ await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> client().when()
+ .toolsList(page -> assertThat(toolNames(page)).contains("say_hello"))
+ .thenAssertResults());
+ }
+
+ private static void controlRoute(String action) {
+ RestAssured.given()
+ .when()
+ .post("/mcp-server/route/say-hello-route/" + action)
+ .then()
+ .statusCode(204);
+ }
+
+ private static String routeStatus() {
+ return RestAssured.given()
+ .when()
+ .get("/mcp-server/route/say-hello-route/status")
+ .then()
+ .statusCode(200)
+ .extract()
+ .body()
+ .asString();
+ }
+
+ private static List toolNames(ToolsPage page) {
+ return page.tools().stream().map(ToolInfo::name).toList();
+ }
+
+ private static String textOf(ToolResponse response) {
+ return response.content().stream()
+ .filter(TextContent.class::isInstance)
+ .map(content -> ((TextContent) content).text())
+ .collect(Collectors.joining());
+ }
+}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 8ffee5a4dd6..3eac678682e 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -179,6 +179,7 @@
master-file
master-infinispan
master-openshift
+ mcp-server
mdc
micrometer
micrometer-observability
diff --git a/pom.xml b/pom.xml
index bf1e32703e6..980459421a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,7 @@
3.6.2
3.2.0
1.12.2
+ 1.13.1
3.5.0
3.9.1
2.4.2
diff --git a/poms/bom-test/pom.xml b/poms/bom-test/pom.xml
index ca3cfd8ecf3..5454654acaf 100644
--- a/poms/bom-test/pom.xml
+++ b/poms/bom-test/pom.xml
@@ -463,6 +463,12 @@
${microsoft.graph.auth.version}
test
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-test
+ ${quarkiverse-mcp-server.version}
+ test
+
diff --git a/poms/bom/pom.xml b/poms/bom/pom.xml
index ef15e3593ed..f2c20ce769f 100644
--- a/poms/bom/pom.xml
+++ b/poms/bom/pom.xml
@@ -2310,6 +2310,11 @@
camel-master
${camel.version}
+
+ org.apache.camel
+ camel-mcp-server-api
+ ${camel.version}
+
org.apache.camel
camel-mdc
@@ -5650,6 +5655,16 @@
camel-quarkus-master-deployment
${camel-quarkus.version}
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+ ${camel-quarkus.version}
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-deployment
+ ${camel-quarkus.version}
+
org.apache.camel.quarkus
camel-quarkus-mdc
@@ -7932,6 +7947,16 @@
quarkus-jsch-deployment
${quarkiverse-jsch.version}
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http
+ ${quarkiverse-mcp-server.version}
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http-deployment
+ ${quarkiverse-mcp-server.version}
+
io.quarkiverse.messaginghub
quarkus-pooled-jms
diff --git a/poms/bom/src/main/generated/flattened-full-pom.xml b/poms/bom/src/main/generated/flattened-full-pom.xml
index ec5e8408aa7..c75a85ed05a 100644
--- a/poms/bom/src/main/generated/flattened-full-pom.xml
+++ b/poms/bom/src/main/generated/flattened-full-pom.xml
@@ -2223,6 +2223,11 @@
camel-master
4.22.0-SNAPSHOT
+
+ org.apache.camel
+ camel-mcp-server-api
+ 4.22.0-SNAPSHOT
+
org.apache.camel
camel-mdc
@@ -5535,6 +5540,16 @@
camel-quarkus-master-deployment
3.39.0-SNAPSHOT
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+ 3.39.0-SNAPSHOT
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-deployment
+ 3.39.0-SNAPSHOT
+
org.apache.camel.quarkus
camel-quarkus-mdc
@@ -7814,6 +7829,16 @@
quarkus-jsch-deployment
3.2.0
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http
+ 1.13.1
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http-deployment
+ 1.13.1
+
io.quarkiverse.messaginghub
quarkus-pooled-jms
diff --git a/poms/bom/src/main/generated/flattened-reduced-pom.xml b/poms/bom/src/main/generated/flattened-reduced-pom.xml
index 1b36331e8c3..0a66188c965 100644
--- a/poms/bom/src/main/generated/flattened-reduced-pom.xml
+++ b/poms/bom/src/main/generated/flattened-reduced-pom.xml
@@ -1177,6 +1177,16 @@
quarkus-jsch-deployment
3.2.0
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http
+ 1.13.1
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http-deployment
+ 1.13.1
+
io.quarkiverse.messaginghub
quarkus-pooled-jms
@@ -3551,6 +3561,11 @@
camel-master
4.22.0-SNAPSHOT
+
+ org.apache.camel
+ camel-mcp-server-api
+ 4.22.0-SNAPSHOT
+
org.apache.camel
camel-mdc
@@ -6868,6 +6883,16 @@
camel-quarkus-master-deployment
3.39.0-SNAPSHOT
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+ 3.39.0-SNAPSHOT
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-deployment
+ 3.39.0-SNAPSHOT
+
org.apache.camel.quarkus
camel-quarkus-mdc
diff --git a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml
index 4882931d0d7..66614a6368b 100644
--- a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml
+++ b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml
@@ -1177,6 +1177,16 @@
quarkus-jsch-deployment
3.2.0
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http
+ 1.13.1
+
+
+ io.quarkiverse.mcp
+ quarkus-mcp-server-http-deployment
+ 1.13.1
+
io.quarkiverse.messaginghub
quarkus-pooled-jms
@@ -3551,6 +3561,11 @@
camel-master
4.22.0-SNAPSHOT
+
+ org.apache.camel
+ camel-mcp-server-api
+ 4.22.0-SNAPSHOT
+
org.apache.camel
camel-mdc
@@ -6868,6 +6883,16 @@
camel-quarkus-master-deployment
3.39.0-SNAPSHOT
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server
+ 3.39.0-SNAPSHOT
+
+
+ org.apache.camel.quarkus
+ camel-quarkus-mcp-server-deployment
+ 3.39.0-SNAPSHOT
+
org.apache.camel.quarkus
camel-quarkus-mdc
diff --git a/tooling/scripts/test-categories.yaml b/tooling/scripts/test-categories.yaml
index a4df3902653..98d19bc52c9 100644
--- a/tooling/scripts/test-categories.yaml
+++ b/tooling/scripts/test-categories.yaml
@@ -239,6 +239,7 @@ group-12:
- knative
- langchain4j-web-search
- langchain4j-tools
+ - mcp-server
- openapi-java
- openapi-validator
- paho-mqtt5