This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new ad595d25cb03 CAMEL-24399: Add configurable MCP server metadata to
camel-mcp-server
ad595d25cb03 is described below
commit ad595d25cb038a813d5fb9bda6bdd261ab498c70
Author: Omar Atie <[email protected]>
AuthorDate: Tue Aug 25 03:10:05 2026 -0700
CAMEL-24399: Add configurable MCP server metadata to camel-mcp-server
Expose title, description, website URL, instructions and icons through
McpServerConfiguration and camel.server.mcp-* properties so Vert.x MCP
servers advertise full serverInfo metadata on initialize. Includes catalog
and main-configuration metadata regeneration, docs, and tests.
Closes #25525
Co-authored-by: Omar Atie <[email protected]>
Co-authored-by: Cursor <[email protected]>
---
.../org/apache/camel/catalog/docs/main.adoc | 7 +-
.../org/apache/camel/catalog/docs/mcp-server.adoc | 22 +++++
.../main/camel-main-configuration-metadata.json | 5 +
.../component/mcp/server/McpServerBridge.java | 24 ++++-
.../mcp/server/McpServerConfiguration.java | 70 +++++++++++++-
.../camel/component/mcp/server/McpServerIcon.java | 45 +++++++++
.../camel/component/mcp/server/McpServerInfo.java | 26 ++++-
.../component/mcp/server/McpServerBridgeTest.java | 36 +++++++
.../component/mcp/server/McpServerIconTest.java | 44 +++++++++
.../camel-mcp-server/src/main/docs/mcp-server.adoc | 22 +++++
.../mcp/server/main/DefaultMcpServerFactory.java | 5 +
.../mcp/server/main/McpServerIconsParser.java | 86 +++++++++++++++++
.../mcp/server/vertx/VertxMcpServerEngine.java | 49 +++++++++-
.../mcp/server/main/McpServerIconsParserTest.java | 95 ++++++++++++++++++
.../mcp/server/main/McpServerMainMetadataTest.java | 98 +++++++++++++++++++
...ttpServerConfigurationPropertiesConfigurer.java | 35 +++++++
.../camel-main-configuration-metadata.json | 5 +
core/camel-main/src/main/docs/main.adoc | 7 +-
.../main/HttpServerConfigurationProperties.java | 106 +++++++++++++++++++++
19 files changed, 773 insertions(+), 14 deletions(-)
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 8ed7976b3865..869f38ff8ff3 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 28 options, which are listed below.
+The camel.server supports 33 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -216,8 +216,13 @@ The camel.server supports 28 options, which are listed
below.
| *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.mcpInstructions* | Top-level MCP instructions returned to
clients on initialize. | | String
| *camel.server.mcpPath* | HTTP path where the MCP endpoint is served. | /mcp
| String
+| *camel.server.mcpServerDescription* | MCP server description advertised to
clients in serverInfo.description . | | String
+| *camel.server.mcpServerIcons* | JSON array of MCP server icons advertised to
clients in serverInfo.icons . Each entry must include a src URL and may include
mimeType , sizes and theme . | | String
| *camel.server.mcpServerName* | MCP server name advertised to clients.
Defaults to the CamelContext name. | | String
+| *camel.server.mcpServerTitle* | MCP server display title advertised to
clients in serverInfo.title . | | String
+| *camel.server.mcpServerWebsiteUrl* | MCP server website URL advertised to
clients in serverInfo.websiteUrl . | | String
| *camel.server.mcpSessionIdleTtl* | Idle TTL in milliseconds for MCP sessions
on the Vert.x streamable transport. Sessions with no activity for longer than
this interval are evicted. 0 disables idle eviction. | 300000 | long
| *camel.server.mcpSessionKeepAliveInterval* | Keep-alive ping interval in
milliseconds for MCP sessions on the Vert.x streamable transport. Dead sessions
are evicted after consecutive ping failures. 0 disables keep-alive pings. |
30000 | long
| *camel.server.mcpTags* | Comma-separated list of ai-tool tag patterns to
expose as MCP tools. Matching is case-insensitive and supports exact match,
wildcard prefix ( {code foo} ), and {code } to match all tags. Only tools
registered under a matching tag are exposed; the untagged default pool is never
exposed. When not set, no tools are exposed. | | 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 6b761dc26029..39564e2eaf1c 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
@@ -118,6 +118,11 @@ camel.server.enabled = true
camel.server.mcp-enabled = true
camel.server.mcp-tags = crm,notify
camel.server.mcp-server-name = my-integration-app
+camel.server.mcp-server-title = My Integration MCP
+camel.server.mcp-server-description = Tools for customer operations
+camel.server.mcp-server-website-url = https://example.com/docs
+camel.server.mcp-instructions = Use these tools to operate the integration.
+camel.server.mcp-server-icons =
[{"src":"https://example.com/icon.png","mimeType":"image/png","sizes":["48x48"],"theme":"light"}]
----
Tag patterns support wildcards: use `*` to expose all tagged tools, or a
@@ -130,6 +135,12 @@ On other runtimes, or when wiring programmatically, add the
----
McpServerConfiguration configuration = new McpServerConfiguration();
configuration.setTags("crm,notify");
+configuration.setServerName("my-integration-app");
+configuration.setServerTitle("My Integration MCP");
+configuration.setServerDescription("Tools for customer operations");
+configuration.setServerWebsiteUrl("https://example.com/docs");
+configuration.setInstructions("Use these tools to operate the integration.");
+configuration.setServerIcons(List.of(new
McpServerIcon("https://example.com/icon.png", "image/png", List.of("48x48"),
"light")));
camelContext.addService(new McpServerBridge(configuration));
----
@@ -173,6 +184,17 @@ JBang (see the xref:main.adoc[camel-main] options) or on
`/mcp` | engine
| `camel.server.mcp-server-name` | MCP server name advertised to clients. |
CamelContext name | engine
+| `camel.server.mcp-server-title` | MCP server display title advertised to
clients in
+ `serverInfo.title`. | | engine
+| `camel.server.mcp-server-description` | MCP server description advertised to
clients in
+ `serverInfo.description`. | | engine
+| `camel.server.mcp-server-website-url` | MCP server website URL advertised to
clients in
+ `serverInfo.websiteUrl`. | | engine
+| `camel.server.mcp-instructions` | Top-level MCP instructions returned to
clients on
+ initialize (not part of `serverInfo`). | | engine
+| `camel.server.mcp-server-icons` | JSON array of MCP server icons advertised
to clients in
+ `serverInfo.icons`. Each entry must include a `src` URL and may include
+ `mimeType`, `sizes` and `theme`. | | engine
| `camel.server.mcp-session-keep-alive-interval` | Keep-alive ping interval in
milliseconds for MCP sessions on the Vert.x streamable transport. Dead
sessions
are evicted after consecutive ping failures. `0` disables keep-alive
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 6287ad28e0ae..fc0b05196272 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
@@ -441,8 +441,13 @@
{ "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.mcpInstructions", "required": false,
"description": "Top-level MCP instructions returned to clients on initialize.",
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "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.mcpServerDescription", "required": false,
"description": "MCP server description advertised to clients in
serverInfo.description .", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
+ { "name": "camel.server.mcpServerIcons", "required": false, "description":
"JSON array of MCP server icons advertised to clients in serverInfo.icons .
Each entry must include a src URL and may include mimeType , sizes and theme
.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "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.mcpServerTitle", "required": false, "description":
"MCP server display title advertised to clients in serverInfo.title .",
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.server.mcpServerWebsiteUrl", "required": false,
"description": "MCP server website URL advertised to clients in
serverInfo.websiteUrl .", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.mcpSessionIdleTtl", "required": false,
"description": "Idle TTL in milliseconds for MCP sessions on the Vert.x
streamable transport. Sessions with no activity for longer than this interval
are evicted. 0 disables idle eviction.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer",
"javaType": "long", "defaultValue": 300000, "secret": false },
{ "name": "camel.server.mcpSessionKeepAliveInterval", "required": false,
"description": "Keep-alive ping interval in milliseconds for MCP sessions on
the Vert.x streamable transport. Dead sessions are evicted after consecutive
ping failures. 0 disables keep-alive pings.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer",
"javaType": "long", "defaultValue": 30000, "secret": false },
{ "name": "camel.server.mcpTags", "required": false, "description":
"Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching
is case-insensitive and supports exact match, wildcard prefix ( {code foo} ),
and {code } to match all tags. Only tools registered under a matching tag are
exposed; the untagged default pool is never exposed. When not set, no tools are
exposed.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"ja [...]
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
index 277833c5b3ec..ae1b887e8c7b 100644
---
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerBridge.java
@@ -121,7 +121,10 @@ public class McpServerBridge extends ServiceSupport
implements CamelContextAware
}
engine.initialize(new McpServerInfo(
serverName, version, configuration.getPath(),
- configuration.getSessionKeepAliveInterval(),
configuration.getSessionIdleTtl()));
+ configuration.getSessionKeepAliveInterval(),
configuration.getSessionIdleTtl(),
+ configuration.getServerTitle(),
configuration.getServerDescription(),
+ configuration.getServerWebsiteUrl(),
configuration.getInstructions(),
+ configuration.getServerIcons()));
if (!engine.consumesServingConfiguration()) {
if
(!McpServerConstants.DEFAULT_PATH.equals(configuration.getPath())) {
@@ -129,9 +132,9 @@ public class McpServerBridge extends ServiceSupport
implements CamelContextAware
+ "decides the endpoint path",
engine.getClass().getSimpleName());
}
- if (configuration.getServerName() != null) {
- LOG.warn("The MCP serverName option may be ignored by engine
{}: the runtime's native MCP server "
- + "configuration decides the server identity",
+ if (hasServerMetadataConfiguration()) {
+ LOG.warn("The MCP server metadata options may be ignored by
engine {}: the runtime's native MCP "
+ + "server configuration decides the server identity
and initialize metadata",
engine.getClass().getSimpleName());
}
}
@@ -172,6 +175,19 @@ public class McpServerBridge extends ServiceSupport
implements CamelContextAware
}
}
+ private boolean hasServerMetadataConfiguration() {
+ return isSet(configuration.getServerName())
+ || isSet(configuration.getServerTitle())
+ || isSet(configuration.getServerDescription())
+ || isSet(configuration.getServerWebsiteUrl())
+ || isSet(configuration.getInstructions())
+ || (configuration.getServerIcons() != null &&
!configuration.getServerIcons().isEmpty());
+ }
+
+ private static boolean isSet(String value) {
+ return value != null && !value.isBlank();
+ }
+
private McpServerEngine resolveEngine() {
McpServerEngine answer =
camelContext.getRegistry().findSingleByType(McpServerEngine.class);
if (answer == null) {
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java
index 09fed0658340..1af4a1ad138d 100644
---
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerConfiguration.java
@@ -16,12 +16,15 @@
*/
package org.apache.camel.component.mcp.server;
+import java.util.List;
+
/**
* Configuration for the {@link McpServerBridge}.
* <p>
* Bridge-owned options ({@code tags}, {@code toolTimeout}) are honored on
every runtime. Engine-owned options
- * ({@code path}, {@code serverName}) are consumed only by engines that serve
through Camel — native engines (Quarkus,
- * Spring Boot) use their own runtime configuration instead.
+ * ({@code path}, {@code serverName}, {@code serverTitle}, {@code
serverDescription}, {@code serverWebsiteUrl},
+ * {@code instructions}, {@code serverIcons}) are consumed only by engines
that serve through Camel — native engines
+ * (Quarkus, Spring Boot) use their own runtime configuration instead.
*
* @since 4.22
*/
@@ -31,6 +34,11 @@ public class McpServerConfiguration {
private long toolTimeout = McpServerConstants.DEFAULT_TOOL_TIMEOUT;
private String path = McpServerConstants.DEFAULT_PATH;
private String serverName;
+ private String serverTitle;
+ private String serverDescription;
+ private String serverWebsiteUrl;
+ private String instructions;
+ private List<McpServerIcon> serverIcons;
private long sessionKeepAliveInterval =
McpServerConstants.DEFAULT_SESSION_KEEP_ALIVE_INTERVAL;
private long sessionIdleTtl = McpServerConstants.DEFAULT_SESSION_IDLE_TTL;
@@ -82,6 +90,64 @@ public class McpServerConfiguration {
this.serverName = serverName;
}
+ /**
+ * MCP server display title advertised to clients in {@code
serverInfo.title}. Engine-owned hint: native engines MAY
+ * ignore it.
+ */
+ public String getServerTitle() {
+ return serverTitle;
+ }
+
+ public void setServerTitle(String serverTitle) {
+ this.serverTitle = serverTitle;
+ }
+
+ /**
+ * MCP server description advertised to clients in {@code
serverInfo.description}. Engine-owned hint: native engines
+ * MAY ignore it.
+ */
+ public String getServerDescription() {
+ return serverDescription;
+ }
+
+ public void setServerDescription(String serverDescription) {
+ this.serverDescription = serverDescription;
+ }
+
+ /**
+ * MCP server website URL advertised to clients in {@code
serverInfo.websiteUrl}. Engine-owned hint: native engines
+ * MAY ignore it.
+ */
+ public String getServerWebsiteUrl() {
+ return serverWebsiteUrl;
+ }
+
+ public void setServerWebsiteUrl(String serverWebsiteUrl) {
+ this.serverWebsiteUrl = serverWebsiteUrl;
+ }
+
+ /**
+ * Top-level MCP instructions returned to clients on initialize.
Engine-owned hint: native engines MAY ignore it.
+ */
+ public String getInstructions() {
+ return instructions;
+ }
+
+ public void setInstructions(String instructions) {
+ this.instructions = instructions;
+ }
+
+ /**
+ * Icons advertised to clients in {@code serverInfo.icons}. Engine-owned
hint: native engines MAY ignore them.
+ */
+ public List<McpServerIcon> getServerIcons() {
+ return serverIcons;
+ }
+
+ public void setServerIcons(List<McpServerIcon> serverIcons) {
+ this.serverIcons = serverIcons;
+ }
+
/**
* Keep-alive ping interval in milliseconds for the Vert.x streamable
transport. Dead sessions are evicted after
* consecutive ping failures. {@code 0} disables keep-alive pings.
Engine-owned: ignored by native engines.
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerIcon.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerIcon.java
new file mode 100644
index 000000000000..31d9652142ad
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerIcon.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * Icon metadata advertised in the MCP {@code serverInfo} block.
+ *
+ * @param src icon URL (required)
+ * @param mimeType optional MIME type (for example {@code image/png})
+ * @param sizes optional size descriptors (for example {@code 48x48})
+ * @param theme optional theme hint (for example {@code light} or {@code
dark})
+ *
+ * @since 4.23
+ */
+public record McpServerIcon(String src, String mimeType, List<String> sizes,
String theme) {
+
+ public McpServerIcon {
+ if (src == null || src.isBlank()) {
+ throw new IllegalArgumentException("src must be set");
+ }
+ if (sizes != null) {
+ sizes = List.copyOf(sizes);
+ }
+ }
+
+ public McpServerIcon(String src) {
+ this(src, null, null, null);
+ }
+}
diff --git
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerInfo.java
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerInfo.java
index 9e13bfb4baa0..5169a3426e97 100644
---
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerInfo.java
+++
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerInfo.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.component.mcp.server;
+import java.util.List;
+
/**
* Identity and serving hints passed to an {@link McpServerEngine} before it
is started.
* <p>
@@ -29,6 +31,11 @@ package org.apache.camel.component.mcp.server;
* {@code 0} disables keep-alive pings
* @param sessionIdleTtlMs idle TTL in milliseconds; sessions with
no activity for longer are evicted;
* {@code 0} disables idle eviction
+ * @param title optional display title advertised in
{@code serverInfo}
+ * @param description optional human-readable description
advertised in {@code serverInfo}
+ * @param websiteUrl optional documentation or home page URL
advertised in {@code serverInfo}
+ * @param instructions optional top-level instructions returned
to MCP clients on initialize
+ * @param icons optional icons advertised in {@code
serverInfo}
*
* @since 4.22
*/
@@ -37,12 +44,24 @@ public record McpServerInfo(
String version,
String path,
long sessionKeepAliveIntervalMs,
- long sessionIdleTtlMs) {
+ long sessionIdleTtlMs,
+ String title,
+ String description,
+ String websiteUrl,
+ String instructions,
+ List<McpServerIcon> icons) {
public McpServerInfo(String serverName, String version, String path) {
this(serverName, version, path,
McpServerConstants.DEFAULT_SESSION_KEEP_ALIVE_INTERVAL,
- McpServerConstants.DEFAULT_SESSION_IDLE_TTL);
+ McpServerConstants.DEFAULT_SESSION_IDLE_TTL,
+ null, null, null, null, null);
+ }
+
+ public McpServerInfo(String serverName, String version, String path,
+ long sessionKeepAliveIntervalMs, long
sessionIdleTtlMs) {
+ this(serverName, version, path, sessionKeepAliveIntervalMs,
sessionIdleTtlMs,
+ null, null, null, null, null);
}
public McpServerInfo {
@@ -52,5 +71,8 @@ public record McpServerInfo(
if (sessionIdleTtlMs < 0) {
throw new IllegalArgumentException("sessionIdleTtlMs must be >=
0");
}
+ if (icons != null) {
+ icons = List.copyOf(icons);
+ }
}
}
diff --git
a/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
index 26c927ba5f36..d11d3ef2a177 100644
---
a/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
+++
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerBridgeTest.java
@@ -16,10 +16,12 @@
*/
package org.apache.camel.component.mcp.server;
+import java.util.List;
import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Test;
@@ -183,6 +185,40 @@ class McpServerBridgeTest extends CamelTestSupport {
assertThat(result.structuredContent()).isNotNull();
}
+ @Test
+ void testServerMetadataPassedToEngine() throws Exception {
+ CamelContext metadataContext = new DefaultCamelContext();
+ RecordingMcpServerEngine metadataEngine = new
RecordingMcpServerEngine();
+ metadataContext.getRegistry().bind("mcpServerEngine", metadataEngine);
+ McpServerConfiguration configuration = new McpServerConfiguration();
+ configuration.setTags("crm");
+ configuration.setServerName("wanaku-camel-mcp");
+ configuration.setServerTitle("Wanaku Camel MCP");
+ configuration.setServerDescription("Integration tools for Wanaku");
+ configuration.setServerWebsiteUrl("https://camel.apache.org/");
+ configuration.setInstructions("Use these tools to operate the
integration.");
+ configuration.setServerIcons(List.of(new McpServerIcon(
+ "https://example.com/icon.png", "image/png", List.of("48x48"),
"light")));
+ McpServerBridge metadataBridge = new McpServerBridge(configuration);
+ metadataContext.addService(metadataBridge);
+ metadataContext.start();
+ metadataBridge.start();
+
+ try {
+ McpServerInfo info = metadataEngine.info();
+ assertThat(info.serverName()).isEqualTo("wanaku-camel-mcp");
+ assertThat(info.title()).isEqualTo("Wanaku Camel MCP");
+ assertThat(info.description()).isEqualTo("Integration tools for
Wanaku");
+
assertThat(info.websiteUrl()).isEqualTo("https://camel.apache.org/");
+ assertThat(info.instructions()).isEqualTo("Use these tools to
operate the integration.");
+ assertThat(info.icons()).hasSize(1);
+
assertThat(info.icons().get(0).src()).isEqualTo("https://example.com/icon.png");
+ } finally {
+ metadataBridge.stop();
+ metadataContext.stop();
+ }
+ }
+
@Test
void testNameCollisionIsRefused() throws Exception {
McpServerTool published = engine.tools().get("query_db");
diff --git
a/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerIconTest.java
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerIconTest.java
new file mode 100644
index 000000000000..3337db032ad3
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerIconTest.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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class McpServerIconTest {
+
+ @Test
+ void testRejectsBlankSrc() {
+ assertThatThrownBy(() -> new McpServerIcon(" "))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("src");
+ }
+
+ @Test
+ void testSizesAreDefensivelyCopied() {
+ List<String> sizes = new ArrayList<>(List.of("48x48"));
+ McpServerIcon icon = new McpServerIcon("https://example.com/icon.png",
"image/png", sizes, "light");
+
+ sizes.add("96x96");
+ assertThat(icon.sizes()).containsExactly("48x48");
+ }
+}
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 6b761dc26029..39564e2eaf1c 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
@@ -118,6 +118,11 @@ camel.server.enabled = true
camel.server.mcp-enabled = true
camel.server.mcp-tags = crm,notify
camel.server.mcp-server-name = my-integration-app
+camel.server.mcp-server-title = My Integration MCP
+camel.server.mcp-server-description = Tools for customer operations
+camel.server.mcp-server-website-url = https://example.com/docs
+camel.server.mcp-instructions = Use these tools to operate the integration.
+camel.server.mcp-server-icons =
[{"src":"https://example.com/icon.png","mimeType":"image/png","sizes":["48x48"],"theme":"light"}]
----
Tag patterns support wildcards: use `*` to expose all tagged tools, or a
@@ -130,6 +135,12 @@ On other runtimes, or when wiring programmatically, add the
----
McpServerConfiguration configuration = new McpServerConfiguration();
configuration.setTags("crm,notify");
+configuration.setServerName("my-integration-app");
+configuration.setServerTitle("My Integration MCP");
+configuration.setServerDescription("Tools for customer operations");
+configuration.setServerWebsiteUrl("https://example.com/docs");
+configuration.setInstructions("Use these tools to operate the integration.");
+configuration.setServerIcons(List.of(new
McpServerIcon("https://example.com/icon.png", "image/png", List.of("48x48"),
"light")));
camelContext.addService(new McpServerBridge(configuration));
----
@@ -173,6 +184,17 @@ JBang (see the xref:main.adoc[camel-main] options) or on
`/mcp` | engine
| `camel.server.mcp-server-name` | MCP server name advertised to clients. |
CamelContext name | engine
+| `camel.server.mcp-server-title` | MCP server display title advertised to
clients in
+ `serverInfo.title`. | | engine
+| `camel.server.mcp-server-description` | MCP server description advertised to
clients in
+ `serverInfo.description`. | | engine
+| `camel.server.mcp-server-website-url` | MCP server website URL advertised to
clients in
+ `serverInfo.websiteUrl`. | | engine
+| `camel.server.mcp-instructions` | Top-level MCP instructions returned to
clients on
+ initialize (not part of `serverInfo`). | | engine
+| `camel.server.mcp-server-icons` | JSON array of MCP server icons advertised
to clients in
+ `serverInfo.icons`. Each entry must include a `src` URL and may include
+ `mimeType`, `sizes` and `theme`. | | engine
| `camel.server.mcp-session-keep-alive-interval` | Keep-alive ping interval in
milliseconds for MCP sessions on the Vert.x streamable transport. Dead
sessions
are evicted after consecutive ping failures. `0` disables keep-alive
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
index e080fdf2c559..426226f89fdf 100644
---
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
@@ -39,6 +39,11 @@ public class DefaultMcpServerFactory implements
McpServerFactory {
mcpConfiguration.setToolTimeout(configuration.getMcpToolTimeout());
mcpConfiguration.setPath(configuration.getMcpPath());
mcpConfiguration.setServerName(configuration.getMcpServerName());
+ mcpConfiguration.setServerTitle(configuration.getMcpServerTitle());
+
mcpConfiguration.setServerDescription(configuration.getMcpServerDescription());
+
mcpConfiguration.setServerWebsiteUrl(configuration.getMcpServerWebsiteUrl());
+ mcpConfiguration.setInstructions(configuration.getMcpInstructions());
+
mcpConfiguration.setServerIcons(McpServerIconsParser.parse(configuration.getMcpServerIcons()));
mcpConfiguration.setSessionKeepAliveInterval(configuration.getMcpSessionKeepAliveInterval());
mcpConfiguration.setSessionIdleTtl(configuration.getMcpSessionIdleTtl());
return new McpServerBridge(mcpConfiguration);
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/McpServerIconsParser.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/McpServerIconsParser.java
new file mode 100644
index 000000000000..51622f504d75
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/main/McpServerIconsParser.java
@@ -0,0 +1,86 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.component.mcp.server.McpServerIcon;
+
+/**
+ * Parses {@code camel.server.mcp-server-icons} JSON into {@link
McpServerIcon} instances.
+ */
+final class McpServerIconsParser {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private McpServerIconsParser() {
+ }
+
+ static List<McpServerIcon> parse(String json) {
+ if (json == null || json.isBlank()) {
+ return null;
+ }
+ try {
+ JsonNode root = MAPPER.readTree(json);
+ if (!root.isArray()) {
+ throw new IllegalArgumentException("mcp-server-icons must be a
JSON array");
+ }
+ List<McpServerIcon> icons = new ArrayList<>(root.size());
+ for (JsonNode node : root) {
+ if (!node.isObject()) {
+ throw new IllegalArgumentException("Each MCP server icon
must be a JSON object");
+ }
+ String src = text(node, "src");
+ if (src == null || src.isBlank()) {
+ throw new IllegalArgumentException("Each MCP server icon
must include a non-blank src");
+ }
+ icons.add(new McpServerIcon(
+ src,
+ text(node, "mimeType"),
+ stringList(node.get("sizes")),
+ text(node, "theme")));
+ }
+ return List.copyOf(icons);
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse
mcp-server-icons JSON", e);
+ }
+ }
+
+ private static String text(JsonNode node, String field) {
+ JsonNode value = node.get(field);
+ return value != null && !value.isNull() ? value.asText() : null;
+ }
+
+ private static List<String> stringList(JsonNode node) {
+ if (node == null || node.isNull()) {
+ return null;
+ }
+ if (!node.isArray()) {
+ throw new IllegalArgumentException("MCP server icon sizes must be
a JSON array");
+ }
+ List<String> values = new ArrayList<>(node.size());
+ for (JsonNode item : node) {
+ values.add(item.asText());
+ }
+ return List.copyOf(values);
+ }
+}
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
index 63ed18dfc355..05c467dd2085 100644
---
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.mcp.server.vertx;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -29,6 +30,7 @@ import org.apache.camel.CamelContext;
import org.apache.camel.component.ai.tool.AiToolAnnotations;
import org.apache.camel.component.mcp.server.McpServerConstants;
import org.apache.camel.component.mcp.server.McpServerEngine;
+import org.apache.camel.component.mcp.server.McpServerIcon;
import org.apache.camel.component.mcp.server.McpServerInfo;
import org.apache.camel.component.mcp.server.McpServerTool;
import org.apache.camel.component.mcp.server.McpToolCallResult;
@@ -107,11 +109,14 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
transport = new VertxMcpStreamableServerTransportProvider(
jsonMapper, info.path(),
info.sessionKeepAliveIntervalMs(), info.sessionIdleTtlMs());
- server = McpServer.sync(transport)
- .serverInfo(info.serverName(), info.version())
+ McpServer.SyncSpecification<?> specification =
McpServer.sync(transport)
+ .serverInfo(buildServerInfo(info))
.capabilities(McpSchema.ServerCapabilities.builder().tools(true).build())
- .immediateExecution(true)
- .build();
+ .immediateExecution(true);
+ if (info.instructions() != null && !info.instructions().isBlank()) {
+ specification.instructions(info.instructions());
+ }
+ server = specification.build();
// register routes only once the server has set the session factory on
the transport
transport.registerRoutes(router);
@@ -217,6 +222,42 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
}
}
+ private static McpSchema.Implementation buildServerInfo(McpServerInfo
info) {
+ McpSchema.Implementation.Builder builder
+ = McpSchema.Implementation.builder(info.serverName(),
info.version());
+ if (info.title() != null && !info.title().isBlank()) {
+ builder.title(info.title());
+ }
+ if (info.description() != null && !info.description().isBlank()) {
+ builder.description(info.description());
+ }
+ if (info.websiteUrl() != null && !info.websiteUrl().isBlank()) {
+ builder.websiteUrl(info.websiteUrl());
+ }
+ if (info.icons() != null && !info.icons().isEmpty()) {
+ builder.icons(mapIcons(info.icons()));
+ }
+ return builder.build();
+ }
+
+ private static List<McpSchema.Icon> mapIcons(List<McpServerIcon> icons) {
+ return icons.stream().map(VertxMcpServerEngine::mapIcon).toList();
+ }
+
+ private static McpSchema.Icon mapIcon(McpServerIcon icon) {
+ McpSchema.Icon.Builder builder = McpSchema.Icon.builder(icon.src());
+ if (icon.mimeType() != null && !icon.mimeType().isBlank()) {
+ builder.mimeType(icon.mimeType());
+ }
+ if (icon.sizes() != null && !icon.sizes().isEmpty()) {
+ builder.sizes(icon.sizes());
+ }
+ if (icon.theme() != null && !icon.theme().isBlank()) {
+ builder.theme(icon.theme());
+ }
+ return builder.build();
+ }
+
private VertxPlatformHttpRouter lookupRouter() {
boolean mainTarget =
VertxPlatformHttpRouter.SERVER_TYPE_SERVER.equals(targetServerType);
Set<VertxPlatformHttpRouter> routers =
camelContext.getRegistry().findByType(VertxPlatformHttpRouter.class);
diff --git
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerIconsParserTest.java
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerIconsParserTest.java
new file mode 100644
index 000000000000..7e765df01c2f
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerIconsParserTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util.List;
+
+import org.apache.camel.component.mcp.server.McpServerIcon;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class McpServerIconsParserTest {
+
+ @Test
+ void testParsesIconsArray() {
+ List<McpServerIcon> icons = McpServerIconsParser.parse("""
+ [
+ {
+ "src": "https://example.com/icon.png",
+ "mimeType": "image/png",
+ "sizes": ["48x48", "96x96"],
+ "theme": "light"
+ }
+ ]
+ """);
+
+ assertThat(icons).hasSize(1);
+
assertThat(icons.get(0).src()).isEqualTo("https://example.com/icon.png");
+ assertThat(icons.get(0).mimeType()).isEqualTo("image/png");
+ assertThat(icons.get(0).sizes()).containsExactly("48x48", "96x96");
+ assertThat(icons.get(0).theme()).isEqualTo("light");
+ }
+
+ @Test
+ void testBlankJsonReturnsNull() {
+ assertThat(McpServerIconsParser.parse(null)).isNull();
+ assertThat(McpServerIconsParser.parse(" ")).isNull();
+ }
+
+ @Test
+ void testRejectsNonArrayJson() {
+ assertThatThrownBy(() ->
McpServerIconsParser.parse("{\"src\":\"https://example.com/icon.png\"}"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("JSON array");
+ }
+
+ @Test
+ void testParsesSrcOnlyIcon() {
+ List<McpServerIcon> icons = McpServerIconsParser.parse("""
+ [{"src":"https://example.com/icon.png"}]
+ """);
+
+ assertThat(icons).hasSize(1);
+
assertThat(icons.get(0).src()).isEqualTo("https://example.com/icon.png");
+ assertThat(icons.get(0).mimeType()).isNull();
+ }
+
+ @Test
+ void testRejectsNonObjectIconEntry() {
+ assertThatThrownBy(() ->
McpServerIconsParser.parse("[\"https://example.com/icon.png\"]"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("JSON object");
+ }
+
+ @Test
+ void testRejectsNonArraySizes() {
+ assertThatThrownBy(() -> McpServerIconsParser.parse("""
+ [{"src":"https://example.com/icon.png","sizes":"48x48"}]
+ """))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("sizes");
+ }
+
+ @Test
+ void testRejectsMissingSrc() {
+ assertThatThrownBy(() ->
McpServerIconsParser.parse("[{\"mimeType\":\"image/png\"}]"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("src");
+ }
+}
diff --git
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainMetadataTest.java
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainMetadataTest.java
new file mode 100644
index 000000000000..506d9c348b99
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainMetadataTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+/**
+ * Verifies CAMEL-24399: additional MCP server metadata is advertised on
initialize.
+ */
+class McpServerMainMetadataTest {
+
+ @Test
+ void testMcpServerMetadataFromProperties() 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")
+ .setBody(constant("Hello"));
+ }
+ });
+ 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",
"wanaku-camel-mcp");
+ main.addInitialProperty("camel.server.mcp-server-title", "Wanaku Camel
MCP");
+ main.addInitialProperty("camel.server.mcp-server-description",
"Integration tools for Wanaku");
+ main.addInitialProperty("camel.server.mcp-server-website-url",
"https://camel.apache.org/");
+ main.addInitialProperty("camel.server.mcp-instructions", "Use these
tools to operate the integration.");
+ main.addInitialProperty("camel.server.mcp-server-icons", """
+
[{"src":"https://example.com/icon.png","mimeType":"image/png","sizes":["48x48"],"theme":"light"}]
+ """);
+ 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();
+ McpSchema.Implementation serverInfo = init.serverInfo();
+
+ assertThat(serverInfo.name()).isEqualTo("wanaku-camel-mcp");
+ assertThat(serverInfo.title()).isEqualTo("Wanaku Camel MCP");
+ assertThat(serverInfo.description()).isEqualTo("Integration tools
for Wanaku");
+
assertThat(serverInfo.websiteUrl()).isEqualTo("https://camel.apache.org/");
+ assertThat(serverInfo.version()).isNotBlank();
+ assertThat(serverInfo.icons()).hasSize(1);
+
assertThat(serverInfo.icons().get(0).src()).isEqualTo("https://example.com/icon.png");
+
assertThat(serverInfo.icons().get(0).mimeType()).isEqualTo("image/png");
+
assertThat(serverInfo.icons().get(0).sizes()).containsExactly("48x48");
+ assertThat(serverInfo.icons().get(0).theme()).isEqualTo("light");
+ assertThat(init.instructions()).isEqualTo("Use these tools to
operate the integration.");
+
+ assertThat(client.listTools().tools())
+ .extracting(McpSchema.Tool::name)
+ .contains("say_hello");
+ assertThat(client.callTool(new
McpSchema.CallToolRequest("say_hello", Map.of())).isError())
+ .isNotEqualTo(Boolean.TRUE);
+ } finally {
+ if (client != null) {
+ client.closeGracefully();
+ }
+ 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 42f63d53a06d..bf84ae5dddcf 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
@@ -38,8 +38,13 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
map.put("JwtKeystoreType", java.lang.String.class);
map.put("MaxBodySize", java.lang.Long.class);
map.put("McpEnabled", boolean.class);
+ map.put("McpInstructions", java.lang.String.class);
map.put("McpPath", java.lang.String.class);
+ map.put("McpServerDescription", java.lang.String.class);
+ map.put("McpServerIcons", java.lang.String.class);
map.put("McpServerName", java.lang.String.class);
+ map.put("McpServerTitle", java.lang.String.class);
+ map.put("McpServerWebsiteUrl", java.lang.String.class);
map.put("McpSessionIdleTtl", long.class);
map.put("McpSessionKeepAliveInterval", long.class);
map.put("McpTags", java.lang.String.class);
@@ -87,10 +92,20 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
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 "mcpinstructions":
+ case "mcpInstructions":
target.setMcpInstructions(property(camelContext, java.lang.String.class,
value)); return true;
case "mcppath":
case "mcpPath": target.setMcpPath(property(camelContext,
java.lang.String.class, value)); return true;
+ case "mcpserverdescription":
+ case "mcpServerDescription":
target.setMcpServerDescription(property(camelContext, java.lang.String.class,
value)); return true;
+ case "mcpservericons":
+ case "mcpServerIcons": target.setMcpServerIcons(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 "mcpservertitle":
+ case "mcpServerTitle": target.setMcpServerTitle(property(camelContext,
java.lang.String.class, value)); return true;
+ case "mcpserverwebsiteurl":
+ case "mcpServerWebsiteUrl":
target.setMcpServerWebsiteUrl(property(camelContext, java.lang.String.class,
value)); return true;
case "mcpsessionidlettl":
case "mcpSessionIdleTtl":
target.setMcpSessionIdleTtl(property(camelContext, long.class, value)); return
true;
case "mcpsessionkeepaliveinterval":
@@ -151,10 +166,20 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
case "maxBodySize": return java.lang.Long.class;
case "mcpenabled":
case "mcpEnabled": return boolean.class;
+ case "mcpinstructions":
+ case "mcpInstructions": return java.lang.String.class;
case "mcppath":
case "mcpPath": return java.lang.String.class;
+ case "mcpserverdescription":
+ case "mcpServerDescription": return java.lang.String.class;
+ case "mcpservericons":
+ case "mcpServerIcons": return java.lang.String.class;
case "mcpservername":
case "mcpServerName": return java.lang.String.class;
+ case "mcpservertitle":
+ case "mcpServerTitle": return java.lang.String.class;
+ case "mcpserverwebsiteurl":
+ case "mcpServerWebsiteUrl": return java.lang.String.class;
case "mcpsessionidlettl":
case "mcpSessionIdleTtl": return long.class;
case "mcpsessionkeepaliveinterval":
@@ -211,10 +236,20 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
case "maxBodySize": return target.getMaxBodySize();
case "mcpenabled":
case "mcpEnabled": return target.isMcpEnabled();
+ case "mcpinstructions":
+ case "mcpInstructions": return target.getMcpInstructions();
case "mcppath":
case "mcpPath": return target.getMcpPath();
+ case "mcpserverdescription":
+ case "mcpServerDescription": return target.getMcpServerDescription();
+ case "mcpservericons":
+ case "mcpServerIcons": return target.getMcpServerIcons();
case "mcpservername":
case "mcpServerName": return target.getMcpServerName();
+ case "mcpservertitle":
+ case "mcpServerTitle": return target.getMcpServerTitle();
+ case "mcpserverwebsiteurl":
+ case "mcpServerWebsiteUrl": return target.getMcpServerWebsiteUrl();
case "mcpsessionidlettl":
case "mcpSessionIdleTtl": return target.getMcpSessionIdleTtl();
case "mcpsessionkeepaliveinterval":
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 6287ad28e0ae..fc0b05196272 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
@@ -441,8 +441,13 @@
{ "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.mcpInstructions", "required": false,
"description": "Top-level MCP instructions returned to clients on initialize.",
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "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.mcpServerDescription", "required": false,
"description": "MCP server description advertised to clients in
serverInfo.description .", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
+ { "name": "camel.server.mcpServerIcons", "required": false, "description":
"JSON array of MCP server icons advertised to clients in serverInfo.icons .
Each entry must include a src URL and may include mimeType , sizes and theme
.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "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.mcpServerTitle", "required": false, "description":
"MCP server display title advertised to clients in serverInfo.title .",
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.server.mcpServerWebsiteUrl", "required": false,
"description": "MCP server website URL advertised to clients in
serverInfo.websiteUrl .", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.mcpSessionIdleTtl", "required": false,
"description": "Idle TTL in milliseconds for MCP sessions on the Vert.x
streamable transport. Sessions with no activity for longer than this interval
are evicted. 0 disables idle eviction.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer",
"javaType": "long", "defaultValue": 300000, "secret": false },
{ "name": "camel.server.mcpSessionKeepAliveInterval", "required": false,
"description": "Keep-alive ping interval in milliseconds for MCP sessions on
the Vert.x streamable transport. Dead sessions are evicted after consecutive
ping failures. 0 disables keep-alive pings.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer",
"javaType": "long", "defaultValue": 30000, "secret": false },
{ "name": "camel.server.mcpTags", "required": false, "description":
"Comma-separated list of ai-tool tag patterns to expose as MCP tools. Matching
is case-insensitive and supports exact match, wildcard prefix ( {code foo} ),
and {code } to match all tags. Only tools registered under a matching tag are
exposed; the untagged default pool is never exposed. When not set, no tools are
exposed.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"ja [...]
diff --git a/core/camel-main/src/main/docs/main.adoc
b/core/camel-main/src/main/docs/main.adoc
index 8ed7976b3865..869f38ff8ff3 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 28 options, which are listed below.
+The camel.server supports 33 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -216,8 +216,13 @@ The camel.server supports 28 options, which are listed
below.
| *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.mcpInstructions* | Top-level MCP instructions returned to
clients on initialize. | | String
| *camel.server.mcpPath* | HTTP path where the MCP endpoint is served. | /mcp
| String
+| *camel.server.mcpServerDescription* | MCP server description advertised to
clients in serverInfo.description . | | String
+| *camel.server.mcpServerIcons* | JSON array of MCP server icons advertised to
clients in serverInfo.icons . Each entry must include a src URL and may include
mimeType , sizes and theme . | | String
| *camel.server.mcpServerName* | MCP server name advertised to clients.
Defaults to the CamelContext name. | | String
+| *camel.server.mcpServerTitle* | MCP server display title advertised to
clients in serverInfo.title . | | String
+| *camel.server.mcpServerWebsiteUrl* | MCP server website URL advertised to
clients in serverInfo.websiteUrl . | | String
| *camel.server.mcpSessionIdleTtl* | Idle TTL in milliseconds for MCP sessions
on the Vert.x streamable transport. Sessions with no activity for longer than
this interval are evicted. 0 disables idle eviction. | 300000 | long
| *camel.server.mcpSessionKeepAliveInterval* | Keep-alive ping interval in
milliseconds for MCP sessions on the Vert.x streamable transport. Dead sessions
are evicted after consecutive ping failures. 0 disables keep-alive pings. |
30000 | long
| *camel.server.mcpTags* | Comma-separated list of ai-tool tag patterns to
expose as MCP tools. Matching is case-insensitive and supports exact match,
wildcard prefix ( {code foo} ), and {code } to match all tags. Only tools
registered under a matching tag are exposed; the untagged default pool is never
exposed. When not set, no tools are exposed. | | String
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 1bf2b23fe5b5..9e452fdcfc69 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
@@ -85,6 +85,16 @@ public class HttpServerConfigurationProperties implements
BootstrapCloseable {
private long mcpSessionIdleTtl = 300000;
@Metadata
private String mcpServerName;
+ @Metadata
+ private String mcpServerTitle;
+ @Metadata
+ private String mcpServerDescription;
+ @Metadata
+ private String mcpServerWebsiteUrl;
+ @Metadata
+ private String mcpInstructions;
+ @Metadata
+ private String mcpServerIcons;
public HttpServerConfigurationProperties(MainConfigurationProperties
parent) {
this.parent = parent;
@@ -400,6 +410,62 @@ public class HttpServerConfigurationProperties implements
BootstrapCloseable {
this.mcpServerName = mcpServerName;
}
+ public String getMcpServerTitle() {
+ return mcpServerTitle;
+ }
+
+ /**
+ * MCP server display title advertised to clients in {@code
serverInfo.title}.
+ */
+ public void setMcpServerTitle(String mcpServerTitle) {
+ this.mcpServerTitle = mcpServerTitle;
+ }
+
+ public String getMcpServerDescription() {
+ return mcpServerDescription;
+ }
+
+ /**
+ * MCP server description advertised to clients in {@code
serverInfo.description}.
+ */
+ public void setMcpServerDescription(String mcpServerDescription) {
+ this.mcpServerDescription = mcpServerDescription;
+ }
+
+ public String getMcpServerWebsiteUrl() {
+ return mcpServerWebsiteUrl;
+ }
+
+ /**
+ * MCP server website URL advertised to clients in {@code
serverInfo.websiteUrl}.
+ */
+ public void setMcpServerWebsiteUrl(String mcpServerWebsiteUrl) {
+ this.mcpServerWebsiteUrl = mcpServerWebsiteUrl;
+ }
+
+ public String getMcpInstructions() {
+ return mcpInstructions;
+ }
+
+ /**
+ * Top-level MCP instructions returned to clients on initialize.
+ */
+ public void setMcpInstructions(String mcpInstructions) {
+ this.mcpInstructions = mcpInstructions;
+ }
+
+ public String getMcpServerIcons() {
+ return mcpServerIcons;
+ }
+
+ /**
+ * JSON array of MCP server icons advertised to clients in {@code
serverInfo.icons}. Each entry must include a
+ * {@code src} URL and may include {@code mimeType}, {@code sizes} and
{@code theme}.
+ */
+ public void setMcpServerIcons(String mcpServerIcons) {
+ this.mcpServerIcons = mcpServerIcons;
+ }
+
public long getMcpSessionKeepAliveInterval() {
return mcpSessionKeepAliveInterval;
}
@@ -648,6 +714,46 @@ public class HttpServerConfigurationProperties implements
BootstrapCloseable {
return this;
}
+ /**
+ * MCP server display title advertised to clients in {@code
serverInfo.title}.
+ */
+ public HttpServerConfigurationProperties withMcpServerTitle(String
mcpServerTitle) {
+ this.mcpServerTitle = mcpServerTitle;
+ return this;
+ }
+
+ /**
+ * MCP server description advertised to clients in {@code
serverInfo.description}.
+ */
+ public HttpServerConfigurationProperties withMcpServerDescription(String
mcpServerDescription) {
+ this.mcpServerDescription = mcpServerDescription;
+ return this;
+ }
+
+ /**
+ * MCP server website URL advertised to clients in {@code
serverInfo.websiteUrl}.
+ */
+ public HttpServerConfigurationProperties withMcpServerWebsiteUrl(String
mcpServerWebsiteUrl) {
+ this.mcpServerWebsiteUrl = mcpServerWebsiteUrl;
+ return this;
+ }
+
+ /**
+ * Top-level MCP instructions returned to clients on initialize.
+ */
+ public HttpServerConfigurationProperties withMcpInstructions(String
mcpInstructions) {
+ this.mcpInstructions = mcpInstructions;
+ return this;
+ }
+
+ /**
+ * JSON array of MCP server icons advertised to clients in {@code
serverInfo.icons}.
+ */
+ public HttpServerConfigurationProperties withMcpServerIcons(String
mcpServerIcons) {
+ this.mcpServerIcons = mcpServerIcons;
+ return this;
+ }
+
/**
* Keep-alive ping interval in milliseconds for MCP sessions on the Vert.x
streamable transport.
*/