This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26665-mcp-allowed-hosts
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 01b1236165c6b91a689b64bfc84fab7450b31a4f
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Nov 12 20:12:38 2026 +0100

    CAMEL-26665: camel-mcp-server - optional allow-list for the Host header on 
/mcp (Camel Main engine)
    
    New option camel.server.mcp-allowed-hosts: a comma-separated list of hosts
    (exact, or host:* for any port or none). Not set by default, and then the
    Host header is not checked, as before. When set, a request on /mcp (POST,
    the SSE GET and DELETE) whose Host is missing or not in the list gets
    HTTP 421 with a JSON-RPC error, before the body is read and before any
    session is created, in the same handler as the Origin check of
    CAMEL-26359. It uses the allowedHosts of the MCP SDK validator.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/docs/main.adoc        |   3 +-
 .../org/apache/camel/catalog/docs/mcp-server.adoc  |  25 +-
 .../main/camel-main-configuration-metadata.json    |   1 +
 .../component/mcp/server/McpServerBridge.java      |   8 +-
 .../mcp/server/McpServerConfiguration.java         |  20 ++
 .../camel/component/mcp/server/McpServerHosts.java | 132 ++++++++
 .../camel/component/mcp/server/McpServerInfo.java  |  46 ++-
 .../component/mcp/server/McpServerBridgeTest.java  |  41 +++
 .../component/mcp/server/McpServerHostsTest.java   |  91 ++++++
 .../camel-mcp-server/src/main/docs/mcp-server.adoc |  25 +-
 .../mcp/server/main/DefaultMcpServerFactory.java   |   1 +
 .../mcp/server/vertx/VertxMcpServerEngine.java     |   6 +-
 .../VertxMcpStreamableServerTransportProvider.java |  84 +++++-
 .../server/main/McpServerMainPropertiesTest.java   |  43 ++-
 .../mcp/server/vertx/VertxMcpHostTest.java         | 331 +++++++++++++++++++++
 ...ttpServerConfigurationPropertiesConfigurer.java |   7 +
 .../camel-main-configuration-metadata.json         |   1 +
 core/camel-main/src/main/docs/main.adoc            |   3 +-
 .../main/HttpServerConfigurationProperties.java    |  28 ++
 19 files changed, 878 insertions(+), 18 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 ebbbbfb72fe8..0270e06c65c8 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
@@ -197,7 +197,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 40 options, which are listed below.
+The camel.server supports 41 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -217,6 +217,7 @@ The camel.server supports 40 options, which are listed 
below.
 | `camel.server.jwtKeystorePath` | Path to the keystore file used for JWT 
tokens validation. |  | String
 | `camel.server.jwtKeystoreType` | Type of the keystore used for JWT tokens 
validation (jks, pkcs12, etc.). |  | String
 | `camel.server.maxBodySize` | Maximum HTTP body size the embedded HTTP server 
can accept. |  | Long
+| `camel.server.mcpAllowedHosts` | Comma-separated list of hosts allowed in 
the Host header of a request to the MCP endpoint on the Vert.x streamable 
transport, such as mcp.example.com or localhost:*. Each entry is a host, or a 
host and a port, where the port may be an asterisk to match any port (or none). 
The host is compared ignoring case. When set, a request whose Host header is 
missing or not in the list is rejected with HTTP 421 (Misdirected Request), 
before its body is read and bef [...]
 | `camel.server.mcpAllowedOrigins` | Comma-separated list of origins allowed 
to call the MCP endpoint on the Vert.x streamable transport. Each entry is an 
exact origin (scheme, host and port), or ends with a colon and an asterisk to 
match any port. Scheme and host are compared ignoring case, and a default port 
(80 for http, 443 for https) may be left out. A wildcard for all origins is not 
supported: an entry that is not of the form scheme://host or scheme://host:port 
fails the startup. A [...]
 | `camel.server.mcpEnabled` | Whether to expose ai-tool routes as MCP tools, 
ai-resource routes as MCP resources and ai-prompt routes as MCP prompts. 
Requires camel-mcp-server on the classpath. Use setMcpTransport(String) to 
choose streamable HTTP (default) or stdio for IDE subprocess integration. By 
default, the MCP server is not enabled. | false | boolean
 | `camel.server.mcpInstructions` | Top-level MCP instructions returned to 
clients on initialize. |  | 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 cdf1ca7600b1..5767742c4f41 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
@@ -522,12 +522,35 @@ path such as `+https://app.example.com/mcp+`, an entry 
without a scheme such as
 
 A refused request is logged at `DEBUG` level by the
 
`org.apache.camel.component.mcp.server.vertx.VertxMcpStreamableServerTransportProvider`
-logger, with the `Origin` it carried.
+logger, with the `Origin` (or the `Host`) it carried.
 
 The check is independent of the platform-http CORS configuration: CORS only
 adds response headers for the browser, while the origin check refuses the
 request on the server.
 
+The `Host` header can be checked as well, with
+`camel.server.mcp-allowed-hosts`. It is not set by default, and then the `Host`
+header is not checked. When set, a request whose `Host` header is missing or 
not
+in the list gets HTTP 421 (Misdirected Request) with a JSON-RPC error, before
+its body is read and before any session is created. This protects a server on a
+private address against DNS rebinding: a hostile web page whose own host name
+resolves to the address of the server sends its own host name in the `Host`
+header, not one from the list.
+
+[source,properties]
+----
+camel.server.mcp-allowed-hosts = mcp.example.com, localhost:*, 127.0.0.1:*
+----
+
+Each entry is a host, or a host and a port, without a scheme. A port of `+*+`
+matches any port, and also a `Host` header without a port (the default port of
+the scheme), so `localhost:*` allows `localhost`, `localhost:8080` and
+`localhost:9876`. An entry without a port matches only a `Host` header without
+a port: `mcp.example.com` is the host name a client uses on the default port,
+behind a reverse proxy for example. The host is compared ignoring case. Write 
an
+IPv6 address in brackets, such as `[::1]:*`. An entry with a scheme or a path,
+and `+*+`, fail the startup with a message saying why.
+
 The main HTTP server listens on all network interfaces by default
 (`camel.server.host = 0.0.0.0`). When the MCP server is only meant for clients
 on the same machine, bind it to the loopback interface:
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 1b2d7a784d63..dfa566aca830 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
@@ -463,6 +463,7 @@
     { "name": "camel.server.jwtKeystorePath", "required": false, 
"description": "Path to the keystore file used for JWT tokens validation.", 
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", 
"type": "string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.server.jwtKeystoreType", "required": false, 
"description": "Type of the keystore used for JWT tokens validation (jks, 
pkcs12, etc.).", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "secret": false },
     { "name": "camel.server.maxBodySize", "required": false, "description": 
"Maximum HTTP body size the embedded HTTP server can accept.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", 
"javaType": "java.lang.Long", "secret": false },
+    { "name": "camel.server.mcpAllowedHosts", "required": false, 
"description": "Comma-separated list of hosts allowed in the Host header of a 
request to the MCP endpoint on the Vert.x streamable transport, such as 
mcp.example.com or localhost:*. Each entry is a host, or a host and a port, 
where the port may be an asterisk to match any port (or none). The host is 
compared ignoring case. When set, a request whose Host header is missing or not 
in the list is rejected with HTTP 421 (Misdire [...]
     { "name": "camel.server.mcpAllowedOrigins", "required": false, 
"description": "Comma-separated list of origins allowed to call the MCP 
endpoint on the Vert.x streamable transport. Each entry is an exact origin 
(scheme, host and port), or ends with a colon and an asterisk to match any 
port. Scheme and host are compared ignoring case, and a default port (80 for 
http, 443 for https) may be left out. A wildcard for all origins is not 
supported: an entry that is not of the form scheme:\/\ [...]
     { "name": "camel.server.mcpEnabled", "required": false, "description": 
"Whether to expose ai-tool routes as MCP tools, ai-resource routes as MCP 
resources and ai-prompt routes as MCP prompts. Requires camel-mcp-server on the 
classpath. Use setMcpTransport(String) to choose streamable HTTP (default) or 
stdio for IDE subprocess integration. By default, the MCP server is not 
enabled.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType" [...]
     { "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 },
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 d1facd6d9923..e3eab52bbe58 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
@@ -158,7 +158,8 @@ public class McpServerBridge extends ServiceSupport 
implements CamelContextAware
                 configuration.getServerIcons(),
                 
McpServerOrigins.parseAllowedOrigins(configuration.getAllowedOrigins()),
                 configuration.getOauthProfile(), 
configuration.getRequiredScopes(),
-                configuration.getResourceUri()));
+                configuration.getResourceUri(),
+                
McpServerHosts.parseAllowedHosts(configuration.getAllowedHosts())));
 
         if (!engine.consumesServingConfiguration()) {
             if 
(!McpServerConstants.DEFAULT_PATH.equals(configuration.getPath())) {
@@ -171,6 +172,11 @@ public class McpServerBridge extends ServiceSupport 
implements CamelContextAware
                          + "configuration decides which origins are allowed",
                         engine.getClass().getSimpleName());
             }
+            if (isSet(configuration.getAllowedHosts())) {
+                LOG.warn("The MCP allowed hosts option is ignored by engine 
{}: the runtime's native MCP server "
+                         + "configuration decides which hosts are allowed",
+                        engine.getClass().getSimpleName());
+            }
             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",
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 ee8c293b8d3d..50477086a1ad 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
@@ -45,6 +45,7 @@ public class McpServerConfiguration {
     private long sessionKeepAliveInterval = 
McpServerConstants.DEFAULT_SESSION_KEEP_ALIVE_INTERVAL;
     private long sessionIdleTtl = McpServerConstants.DEFAULT_SESSION_IDLE_TTL;
     private String allowedOrigins;
+    private String allowedHosts;
     private String oauthProfile;
     private String requiredScopes;
     private String resourceUri;
@@ -223,6 +224,25 @@ public class McpServerConfiguration {
         this.allowedOrigins = allowedOrigins;
     }
 
+    /**
+     * Comma-separated list of hosts allowed in the {@code Host} header of a 
request to the MCP endpoint, such as
+     * {@code mcp.example.com} or {@code localhost:*} (any port, or none). 
When set, a request whose {@code Host} header
+     * is missing or not in the list is rejected with HTTP 421 (Misdirected 
Request), before its body is read and before
+     * any session is created; this protects a server bound to a private 
address against DNS rebinding. The host is
+     * compared ignoring case. An entry that is not of the form {@code 
host[:port]}, such as {@code *} or an entry with
+     * a scheme, fails the startup (see {@link McpServerHosts}). When not set, 
the {@code Host} header is not checked.
+     * Engine-owned: ignored by native engines.
+     *
+     * @since 4.24
+     */
+    public String getAllowedHosts() {
+        return allowedHosts;
+    }
+
+    public void setAllowedHosts(String allowedHosts) {
+        this.allowedHosts = allowedHosts;
+    }
+
     /**
      * Name of the camel-oauth profile used to authenticate OAuth bearer 
tokens. Supported by the Camel Main HTTP MCP
      * engine; other engines reject this option unless they explicitly 
implement OAuth support.
diff --git 
a/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerHosts.java
 
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerHosts.java
new file mode 100644
index 000000000000..3e313ef47239
--- /dev/null
+++ 
b/components/camel-ai/camel-mcp-server-api/src/main/java/org/apache/camel/component/mcp/server/McpServerHosts.java
@@ -0,0 +1,132 @@
+/*
+ * 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 java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * Parses and normalizes the hosts allowed in the {@code Host} header of a 
request to the MCP endpoint, and the
+ * {@code Host} header itself, so that both are compared in the same form: the 
host in lower case and the port without
+ * leading zeros.
+ * <p>
+ * An allowed host is {@code host} or {@code host:port}, where the port may be 
{@code *} to match any port, and also a
+ * {@code Host} header without a port. An IPv6 address is written in brackets, 
such as {@code [::1]:*}. Anything else,
+ * such as {@code *}, an entry with a scheme or a path, is refused with an 
{@link IllegalArgumentException}, so a
+ * mistake shows at startup instead of as an error for every request.
+ *
+ * @since 4.24
+ */
+public final class McpServerHosts {
+
+    private static final String ANY_PORT = "*";
+    private static final Pattern HOST = 
Pattern.compile("[a-zA-Z0-9._-]+|\\[[0-9a-fA-F:.]+]");
+    private static final Pattern PORT = Pattern.compile("\\d{1,5}");
+
+    private McpServerHosts() {
+    }
+
+    /**
+     * Splits a comma-separated list of allowed hosts, skipping empty entries, 
and normalizes each entry with
+     * {@link #normalizeAllowedHost(String)}.
+     *
+     * @param  allowedHosts             the comma-separated list
+     * @return                          the normalized hosts, or {@code null} 
when not set or without any entry, so the
+     *                                  {@code Host} header is not checked
+     * @throws IllegalArgumentException when an entry is not a valid host
+     */
+    public static List<String> parseAllowedHosts(String allowedHosts) {
+        if (allowedHosts == null || allowedHosts.isBlank()) {
+            return null;
+        }
+        List<String> answer = new ArrayList<>();
+        for (String entry : allowedHosts.split(",")) {
+            if (!entry.isBlank()) {
+                answer.add(normalizeAllowedHost(entry));
+            }
+        }
+        return answer.isEmpty() ? null : answer;
+    }
+
+    /**
+     * Normalizes an allowed host: trims it, lower-cases the host and drops 
the leading zeros of the port.
+     *
+     * @param  allowedHost              a host such as {@code 
MCP.Example.com}, {@code localhost:8080} or
+     *                                  {@code localhost:*}
+     * @return                          the normalized host, such as {@code 
mcp.example.com}
+     * @throws IllegalArgumentException when the entry is {@code *} or not of 
the form {@code host[:port]}
+     */
+    public static String normalizeAllowedHost(String allowedHost) {
+        String value = allowedHost == null ? "" : allowedHost.trim();
+        if (ANY_PORT.equals(value)) {
+            throw new IllegalArgumentException(
+                    "MCP allowed host * is not supported: leave the allowed 
hosts option unset to not check the Host"
+                                               + " header, or list each host, 
such as mcp.example.com or localhost:* (any port).");
+        }
+        String answer = normalize(value, true);
+        if (answer == null) {
+            throw new IllegalArgumentException(
+                    "Invalid MCP allowed host '" + allowedHost + "': expected 
host or host:port, without a scheme,"
+                                               + " where the port may be * for 
any port, such as mcp.example.com or"
+                                               + " localhost:*");
+        }
+        return answer;
+    }
+
+    /**
+     * Normalizes the value of a {@code Host} header the same way as the 
allowed hosts, so that both compare as strings.
+     *
+     * @param  host the header value
+     * @return      the normalized host, or the value as is when it is not of 
the form {@code host[:port]} (such as
+     *              {@code null}), so it matches no allowed host
+     */
+    public static String normalizeHost(String host) {
+        if (host == null) {
+            return null;
+        }
+        String answer = normalize(host.trim(), false);
+        return answer != null ? answer : host;
+    }
+
+    private static String normalize(String value, boolean allowAnyPort) {
+        String host = value;
+        String port = null;
+        int colon = value.lastIndexOf(':');
+        if (colon >= 0 && colon > value.lastIndexOf(']')) {
+            host = value.substring(0, colon);
+            port = value.substring(colon + 1);
+        }
+        if (!HOST.matcher(host).matches()) {
+            return null;
+        }
+        host = host.toLowerCase(Locale.ROOT);
+        if (port != null) {
+            if (ANY_PORT.equals(port)) {
+                if (!allowAnyPort) {
+                    return null;
+                }
+            } else if (PORT.matcher(port).matches() && Integer.parseInt(port) 
<= 65535) {
+                port = String.valueOf(Integer.parseInt(port));
+            } else {
+                return null;
+            }
+        }
+        return port != null ? host + ":" + port : host;
+    }
+}
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 b06b81f7af6b..fc40fba1e4b7 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
@@ -44,6 +44,9 @@ import java.util.List;
  *                                   disabled
  * @param requiredScopes             comma-separated OAuth scopes required by 
the MCP HTTP endpoint
  * @param resourceUri                explicit public HTTPS URI of the 
protected MCP resource for RFC 9728 metadata
+ * @param allowedHosts               hosts allowed in the {@code Host} header 
of a request to the MCP endpoint (exact,
+ *                                   or {@code host:*} for any port or none); 
{@code null} does not check the
+ *                                   {@code Host} header. Since 4.24.
  *
  * @since                            4.22
  */
@@ -61,19 +64,20 @@ public record McpServerInfo(
         List<String> allowedOrigins,
         String oauthProfile,
         String requiredScopes,
-        String resourceUri) {
+        String resourceUri,
+        List<String> allowedHosts) {
 
     public McpServerInfo(String serverName, String version, String path) {
         this(serverName, version, path,
              McpServerConstants.DEFAULT_SESSION_KEEP_ALIVE_INTERVAL,
              McpServerConstants.DEFAULT_SESSION_IDLE_TTL,
-             null, null, null, null, null, null, null, null, null);
+             null, null, null, null, null, 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, null, null, null, null);
+             null, null, null, null, null, null, null, null, null, null);
     }
 
     public McpServerInfo(String serverName, String version, String path,
@@ -81,7 +85,7 @@ public record McpServerInfo(
                          String title, String description, String websiteUrl, 
String instructions,
                          List<McpServerIcon> icons) {
         this(serverName, version, path, sessionKeepAliveIntervalMs, 
sessionIdleTtlMs,
-             title, description, websiteUrl, instructions, icons, null, null, 
null, null);
+             title, description, websiteUrl, instructions, icons, null, null, 
null, null, null);
     }
 
     /**
@@ -105,7 +109,36 @@ public record McpServerInfo(
                          String title, String description, String websiteUrl, 
String instructions,
                          List<McpServerIcon> icons, List<String> 
allowedOrigins) {
         this(serverName, version, path, sessionKeepAliveIntervalMs, 
sessionIdleTtlMs,
-             title, description, websiteUrl, instructions, icons, 
allowedOrigins, null, null, null);
+             title, description, websiteUrl, instructions, icons, 
allowedOrigins, null, null, null, null);
+    }
+
+    /**
+     * Compatibility constructor preserving the signature from before the 
allowed hosts. The {@code Host} header is not
+     * checked when this constructor is used.
+     *
+     * @param serverName                 the MCP server name
+     * @param version                    the MCP server version
+     * @param path                       the HTTP endpoint path
+     * @param sessionKeepAliveIntervalMs keep-alive interval in milliseconds
+     * @param sessionIdleTtlMs           session idle TTL in milliseconds
+     * @param title                      optional display title
+     * @param description                optional server description
+     * @param websiteUrl                 optional server website URL
+     * @param instructions               optional server instructions
+     * @param icons                      optional server icons
+     * @param allowedOrigins             allowed HTTP origins, or null for the 
default
+     * @param oauthProfile               the camel-oauth profile, or null when 
disabled
+     * @param requiredScopes             comma-separated OAuth scopes required 
by the MCP HTTP endpoint
+     * @param resourceUri                explicit public HTTPS URI of the 
protected MCP resource
+     */
+    public McpServerInfo(String serverName, String version, String path,
+                         long sessionKeepAliveIntervalMs, long 
sessionIdleTtlMs,
+                         String title, String description, String websiteUrl, 
String instructions,
+                         List<McpServerIcon> icons, List<String> 
allowedOrigins,
+                         String oauthProfile, String requiredScopes, String 
resourceUri) {
+        this(serverName, version, path, sessionKeepAliveIntervalMs, 
sessionIdleTtlMs,
+             title, description, websiteUrl, instructions, icons, 
allowedOrigins, oauthProfile, requiredScopes,
+             resourceUri, null);
     }
 
     public McpServerInfo {
@@ -121,5 +154,8 @@ public record McpServerInfo(
         if (allowedOrigins != null) {
             allowedOrigins = List.copyOf(allowedOrigins);
         }
+        if (allowedHosts != null) {
+            allowedHosts = List.copyOf(allowedHosts);
+        }
     }
 }
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 b0411d32aab2..c4908e526813 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
@@ -247,6 +247,47 @@ class McpServerBridgeTest extends CamelTestSupport {
         assertThat(engine.info().allowedOrigins()).isNull();
     }
 
+    @Test
+    void testAllowedHostsPassedToEngine() throws Exception {
+        CamelContext hostContext = new DefaultCamelContext();
+        RecordingMcpServerEngine hostEngine = new RecordingMcpServerEngine();
+        hostContext.getRegistry().bind("mcpServerEngine", hostEngine);
+        McpServerConfiguration configuration = new McpServerConfiguration();
+        configuration.setTags("crm");
+        configuration.setAllowedHosts(" MCP.Example.com ,, localhost:* ");
+        McpServerBridge hostBridge = new McpServerBridge(configuration);
+        hostContext.addService(hostBridge);
+        hostContext.start();
+        hostBridge.start();
+
+        try {
+            
assertThat(hostEngine.info().allowedHosts()).containsExactly("mcp.example.com", 
"localhost:*");
+        } finally {
+            hostBridge.stop();
+            hostContext.stop();
+        }
+    }
+
+    @Test
+    void testAllowedHostsNotSetDoesNotCheckTheHost() {
+        assertThat(engine.info().allowedHosts()).isNull();
+    }
+
+    @Test
+    void testAllowedHostThatCanNeverMatchFailsTheStart() throws Exception {
+        try (DefaultCamelContext hostContext = new DefaultCamelContext()) {
+            hostContext.getRegistry().bind("mcpServerEngine", new 
RecordingMcpServerEngine());
+            McpServerConfiguration configuration = new 
McpServerConfiguration();
+            configuration.setTags("crm");
+            configuration.setAllowedHosts("http://mcp.example.com";);
+            McpServerBridge hostBridge = new McpServerBridge(configuration);
+
+            assertThatThrownBy(() -> hostContext.addService(hostBridge))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessageStartingWith("Invalid MCP allowed host 
'http://mcp.example.com'");
+        }
+    }
+
     @Test
     void testAllowedOriginThatCanNeverMatchFailsTheStart() throws Exception {
         try (DefaultCamelContext originContext = new DefaultCamelContext()) {
diff --git 
a/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerHostsTest.java
 
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerHostsTest.java
new file mode 100644
index 000000000000..3ad7687f3a3e
--- /dev/null
+++ 
b/components/camel-ai/camel-mcp-server-api/src/test/java/org/apache/camel/component/mcp/server/McpServerHostsTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class McpServerHostsTest {
+
+    @Test
+    void notSetTurnsTheCheckOff() {
+        assertThat(McpServerHosts.parseAllowedHosts(null)).isNull();
+        assertThat(McpServerHosts.parseAllowedHosts(" ")).isNull();
+        // a value with no host in it is the same as not set
+        assertThat(McpServerHosts.parseAllowedHosts(",")).isNull();
+    }
+
+    @Test
+    void entriesAreTrimmedAndNormalized() {
+        assertThat(McpServerHosts.parseAllowedHosts(" MCP.Example.com ,, 
LocalHost:* , [::1]:08080 "))
+                .containsExactly("mcp.example.com", "localhost:*", 
"[::1]:8080");
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+            "mcp.example.com, mcp.example.com",
+            "MCP.Example.COM, mcp.example.com",
+            "mcp.example.com:443, mcp.example.com:443",
+            "localhost:08080, localhost:8080",
+            "localhost:*, localhost:*",
+            "127.0.0.1:*, 127.0.0.1:*",
+            "[::1]:*, [::1]:*",
+            "[FE80::1]:3000, [fe80::1]:3000" })
+    void allowedHostIsNormalized(String entry, String expected) {
+        
assertThat(McpServerHosts.normalizeAllowedHost(entry)).isEqualTo(expected);
+    }
+
+    @Test
+    void wildcardForAllHostsIsRefused() {
+        assertThatThrownBy(() -> 
McpServerHosts.parseAllowedHosts("mcp.example.com, *"))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("MCP allowed host * is not supported")
+                .hasMessageContaining("localhost:*");
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {
+            "http://mcp.example.com";, "mcp.example.com/mcp", 
"[email protected]", "*.example.com", "mcp.example.com:",
+            "mcp.example.com:x", "mcp.example.com:70000", "mcp example.com", 
"::1", "[::1" })
+    void entryThatCanNeverMatchIsRefused(String entry) {
+        assertThatThrownBy(() -> McpServerHosts.normalizeAllowedHost(entry))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("Invalid MCP allowed host '" + entry + 
"'");
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+            "mcp.example.com, mcp.example.com",
+            "MCP.Example.com:8080, mcp.example.com:8080",
+            "[::1]:8080, [::1]:8080",
+            "evil..example.com/x, evil..example.com/x" })
+    void hostHeaderIsNormalized(String header, String expected) {
+        assertThat(McpServerHosts.normalizeHost(header)).isEqualTo(expected);
+    }
+
+    @Test
+    void hostHeaderWithAWildcardPortIsKeptAsIs() {
+        // only an allowed host may have a wildcard port; a header with one is 
left as is, it is lower-cased only when valid
+        
assertThat(McpServerHosts.normalizeHost("LocalHost:*")).isEqualTo("LocalHost:*");
+        assertThat(McpServerHosts.normalizeHost(null)).isNull();
+    }
+}
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 cdf1ca7600b1..5767742c4f41 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
@@ -522,12 +522,35 @@ path such as `+https://app.example.com/mcp+`, an entry 
without a scheme such as
 
 A refused request is logged at `DEBUG` level by the
 
`org.apache.camel.component.mcp.server.vertx.VertxMcpStreamableServerTransportProvider`
-logger, with the `Origin` it carried.
+logger, with the `Origin` (or the `Host`) it carried.
 
 The check is independent of the platform-http CORS configuration: CORS only
 adds response headers for the browser, while the origin check refuses the
 request on the server.
 
+The `Host` header can be checked as well, with
+`camel.server.mcp-allowed-hosts`. It is not set by default, and then the `Host`
+header is not checked. When set, a request whose `Host` header is missing or 
not
+in the list gets HTTP 421 (Misdirected Request) with a JSON-RPC error, before
+its body is read and before any session is created. This protects a server on a
+private address against DNS rebinding: a hostile web page whose own host name
+resolves to the address of the server sends its own host name in the `Host`
+header, not one from the list.
+
+[source,properties]
+----
+camel.server.mcp-allowed-hosts = mcp.example.com, localhost:*, 127.0.0.1:*
+----
+
+Each entry is a host, or a host and a port, without a scheme. A port of `+*+`
+matches any port, and also a `Host` header without a port (the default port of
+the scheme), so `localhost:*` allows `localhost`, `localhost:8080` and
+`localhost:9876`. An entry without a port matches only a `Host` header without
+a port: `mcp.example.com` is the host name a client uses on the default port,
+behind a reverse proxy for example. The host is compared ignoring case. Write 
an
+IPv6 address in brackets, such as `[::1]:*`. An entry with a scheme or a path,
+and `+*+`, fail the startup with a message saying why.
+
 The main HTTP server listens on all network interfaces by default
 (`camel.server.host = 0.0.0.0`). When the MCP server is only meant for clients
 on the same machine, bind it to the loopback interface:
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 bdc2510a672e..6a5dfed267fc 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
@@ -61,6 +61,7 @@ public class DefaultMcpServerFactory implements 
McpServerFactory {
         
mcpConfiguration.setSessionKeepAliveInterval(configuration.getMcpSessionKeepAliveInterval());
         
mcpConfiguration.setSessionIdleTtl(configuration.getMcpSessionIdleTtl());
         
mcpConfiguration.setAllowedOrigins(configuration.getMcpAllowedOrigins());
+        mcpConfiguration.setAllowedHosts(configuration.getMcpAllowedHosts());
         // OAuth options have no meaning on stdio. Keep the historical 
warn-and-ignore behavior here,
         // while programmatic bridge configurations fail closed on engines 
that do not support OAuth.
         if (!isStdioTransport(configuration.getMcpTransport())) {
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 724438defdfa..d0175cdea2e2 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
@@ -64,7 +64,9 @@ import org.slf4j.LoggerFactory;
  * <p>
  * Every request on the MCP endpoint that carries an {@code Origin} header 
must come from an allowed origin
  * ({@link McpServerInfo#allowedOrigins()}, loopback origins by default), or 
it is rejected with HTTP 403 before it
- * reaches the MCP server. Requests without an {@code Origin} header (MCP 
clients running as a process) are served.
+ * reaches the MCP server. Requests without an {@code Origin} header (MCP 
clients running as a process) are served. When
+ * allowed hosts are configured ({@link McpServerInfo#allowedHosts()}), every 
request must also carry a {@code Host}
+ * header from that list, or it is rejected with HTTP 421 in the same way.
  */
 @JdkService(McpServerConstants.MCP_SERVER_ENGINE_FACTORY)
 public class VertxMcpServerEngine extends ServiceSupport implements 
McpServerEngine {
@@ -148,7 +150,7 @@ public class VertxMcpServerEngine extends ServiceSupport 
implements McpServerEng
                 jsonMapper, info.path(),
                 info.sessionKeepAliveIntervalMs(), info.sessionIdleTtlMs(), 
info.allowedOrigins(),
                 camelContext, oauthSecurity, info.requiredScopes(), 
info.resourceUri(),
-                authorizationServerIssuer, allowInsecureAuthorizationServer);
+                authorizationServerIssuer, allowInsecureAuthorizationServer, 
info.allowedHosts());
         McpServer.SyncSpecification<?> specification = 
McpServer.sync(transport)
                 .serverInfo(buildServerInfo(info))
                 // resources(subscribe, listChanged): per-resource 
subscriptions are not offered, but clients are
diff --git 
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpStreamableServerTransportProvider.java
 
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpStreamableServerTransportProvider.java
index de2df446ac5c..f1ba4a80816c 100644
--- 
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpStreamableServerTransportProvider.java
+++ 
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpStreamableServerTransportProvider.java
@@ -43,12 +43,14 @@ import io.vertx.core.Context;
 import io.vertx.core.Vertx;
 import io.vertx.core.http.HttpMethod;
 import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.net.HostAndPort;
 import io.vertx.ext.auth.User;
 import io.vertx.ext.web.Route;
 import io.vertx.ext.web.RoutingContext;
 import io.vertx.ext.web.handler.BodyHandler;
 import io.vertx.ext.web.handler.SecurityPolicyHandler;
 import org.apache.camel.CamelContext;
+import org.apache.camel.component.mcp.server.McpServerHosts;
 import org.apache.camel.component.mcp.server.McpServerOrigins;
 import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpRouter;
 import org.apache.camel.http.base.OAuthHttpSecuritySupport;
@@ -70,6 +72,10 @@ import reactor.core.publisher.Mono;
  * Every request carrying an {@code Origin} header is checked against the 
allowed origins before any other handling, as
  * the MCP streamable HTTP transport requires; a request from an origin that 
is not allowed gets HTTP 403 with a
  * JSON-RPC error and never reaches the MCP session. Requests without an 
{@code Origin} header are served.
+ * <p>
+ * When allowed hosts are configured, the {@code Host} header is checked in 
the same handler: a request whose
+ * {@code Host} is missing or not allowed gets HTTP 421 (Misdirected Request), 
which protects a server on a private
+ * address against DNS rebinding. Without allowed hosts the {@code Host} 
header is not checked.
  */
 public class VertxMcpStreamableServerTransportProvider implements 
McpStreamableServerTransportProvider {
 
@@ -86,6 +92,9 @@ public class VertxMcpStreamableServerTransportProvider 
implements McpStreamableS
     private static final String APPLICATION_JSON = "application/json";
     private static final String TEXT_EVENT_STREAM = "text/event-stream";
     private static final String ORIGIN = "Origin";
+    private static final String HOST = "Host";
+    // the status the MCP SDK validator answers a Host that is not allowed with
+    private static final int MISDIRECTED_REQUEST = 421;
 
     /**
      * The origins allowed when none are configured: loopback hosts on any 
port, so browser-based tools on the same
@@ -158,6 +167,32 @@ public class VertxMcpStreamableServerTransportProvider 
implements McpStreamableS
                                                      String resourceUri,
                                                      String 
authorizationServerIssuer,
                                                      boolean 
allowInsecureAuthorizationServer) {
+        this(jsonMapper, path, sessionKeepAliveIntervalMs, sessionIdleTtlMs, 
allowedOrigins, camelContext,
+             oauthSecurity, requiredScopes, resourceUri, 
authorizationServerIssuer, allowInsecureAuthorizationServer,
+             null);
+    }
+
+    /**
+     * Creates the transport with allowed hosts.
+     *
+     * @param allowedHosts the hosts allowed in the {@code Host} header, each 
exact (e.g. {@code mcp.example.com}) or
+     *                     with a wildcard port (e.g. {@code localhost:*}, 
which also allows a {@code Host} without a
+     *                     port), normalized with {@link 
McpServerHosts#normalizeAllowedHost(String)}; {@code null} to
+     *                     not check the {@code Host} header
+     */
+    public VertxMcpStreamableServerTransportProvider(
+                                                     McpJsonMapper jsonMapper,
+                                                     String path,
+                                                     long 
sessionKeepAliveIntervalMs,
+                                                     long sessionIdleTtlMs,
+                                                     List<String> 
allowedOrigins,
+                                                     CamelContext camelContext,
+                                                     OAuthHttpSecuritySupport 
oauthSecurity,
+                                                     String requiredScopes,
+                                                     String resourceUri,
+                                                     String 
authorizationServerIssuer,
+                                                     boolean 
allowInsecureAuthorizationServer,
+                                                     List<String> 
allowedHosts) {
         this.jsonMapper = jsonMapper;
         this.path = path;
         this.sessionKeepAliveIntervalMs = sessionKeepAliveIntervalMs;
@@ -175,9 +210,26 @@ public class VertxMcpStreamableServerTransportProvider 
implements McpStreamableS
                 .allowedOrigins(allowedOrigins != null
                         ? 
allowedOrigins.stream().map(McpServerOrigins::normalizeAllowedOrigin).toList()
                         : DEFAULT_ALLOWED_ORIGINS)
+                .allowedHosts(allowedHosts != null ? 
hostPatterns(allowedHosts) : List.of())
                 .build();
     }
 
+    /**
+     * The hosts as the MCP SDK validator matches them: a {@code host:*} entry 
matches only a {@code Host} with a port
+     * there, so the host without a port (the default port of the scheme) is 
added next to it.
+     */
+    private static List<String> hostPatterns(List<String> allowedHosts) {
+        List<String> answer = new ArrayList<>();
+        for (String entry : allowedHosts) {
+            String host = McpServerHosts.normalizeAllowedHost(entry);
+            answer.add(host);
+            if (host.endsWith(":*")) {
+                answer.add(host.substring(0, host.length() - 2));
+            }
+        }
+        return answer;
+    }
+
     @Override
     public void setSessionFactory(McpStreamableServerSession.Factory 
sessionFactory) {
         this.sessionFactory = sessionFactory;
@@ -262,19 +314,28 @@ public class VertxMcpStreamableServerTransportProvider 
implements McpStreamableS
     }
 
     /**
-     * Serves a request without an {@code Origin} header or from an allowed 
origin; answers any other with HTTP 403 and
-     * a JSON-RPC error that has no {@code id}. Runs on the event loop before 
any other handler of the route.
+     * Serves a request without an {@code Origin} header or from an allowed 
origin, and, when allowed hosts are
+     * configured, with an allowed {@code Host}; answers any other with HTTP 
403 (origin) or 421 (host) and a JSON-RPC
+     * error that has no {@code id}. Runs on the event loop before any other 
handler of the route.
      */
     private void checkOrigin(RoutingContext ctx) {
         try {
             originValidator.validate(new VertxHeaderAccessor(ctx));
         } catch (ServerTransportSecurityException e) {
-            LOG.debug("Rejected MCP {} request on {}: origin {} is not 
allowed", ctx.request().method(), path,
-                    ctx.request().getHeader(ORIGIN));
+            String message;
+            if (e.getStatusCode() == MISDIRECTED_REQUEST) {
+                LOG.debug("Rejected MCP {} request on {}: host {} is not 
allowed", ctx.request().method(), path,
+                        ctx.request().authority());
+                message = "Host not allowed";
+            } else {
+                LOG.debug("Rejected MCP {} request on {}: origin {} is not 
allowed", ctx.request().method(), path,
+                        ctx.request().getHeader(ORIGIN));
+                message = "Origin not allowed";
+            }
             ctx.response()
                     .setStatusCode(e.getStatusCode())
                     .putHeader("Content-Type", APPLICATION_JSON)
-                    .end(errorBody(McpSchema.ErrorCodes.INVALID_REQUEST, 
"Origin not allowed"));
+                    .end(errorBody(McpSchema.ErrorCodes.INVALID_REQUEST, 
message));
             return;
         }
         ctx.next();
@@ -789,6 +850,19 @@ public class VertxMcpStreamableServerTransportProvider 
implements McpStreamableS
 
         @Override
         public List<String> getHeader(String name) {
+            if (HOST.equalsIgnoreCase(name)) {
+                // the authority of the request: the Host header on HTTP/1.1, 
:authority on HTTP/2
+                HostAndPort authority = ctx.request().authority();
+                if (authority == null) {
+                    return List.of();
+                }
+                String host = authority.host();
+                if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
+                    host = "[" + host + "]";
+                }
+                String value = authority.port() >= 0 ? host + ":" + 
authority.port() : host;
+                return List.of(McpServerHosts.normalizeHost(value));
+            }
             List<String> values = ctx.request().headers().getAll(name);
             if (ORIGIN.equalsIgnoreCase(name)) {
                 // compare in the form the allowed origins are kept in: 
lower-case scheme and host, no default port
diff --git 
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java
 
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java
index c3a2a0d97b04..8af1cbda8324 100644
--- 
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java
+++ 
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/main/McpServerMainPropertiesTest.java
@@ -124,6 +124,43 @@ class McpServerMainPropertiesTest {
         }
     }
 
+    @Test
+    void testMcpAllowedHostsFromProperties() throws Exception {
+        int port = AvailablePortFinder.getNextAvailable();
+
+        Main main = new Main();
+        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-allowed-hosts", 
"LocalHost:*");
+        main.start();
+
+        try {
+            assertThat(initialize(URI.create("http://localhost:"; + port + 
"/mcp"), null)).isEqualTo(200);
+            assertThat(initialize(URI.create("http://127.0.0.1:"; + port + 
"/mcp"), null)).isEqualTo(421);
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    void testMcpAllowedHostWithSchemeFailsFast() {
+        Main main = new Main();
+        main.addInitialProperty("camel.server.enabled", "true");
+        main.addInitialProperty("camel.server.port", 
String.valueOf(AvailablePortFinder.getNextAvailable()));
+        main.addInitialProperty("camel.server.mcp-enabled", "true");
+        main.addInitialProperty("camel.server.mcp-tags", "crm");
+        main.addInitialProperty("camel.server.mcp-allowed-hosts", 
"http://localhost:8080";);
+
+        try {
+            assertThatThrownBy(main::start)
+                    .hasStackTraceContaining("Invalid MCP allowed host 
'http://localhost:8080'");
+        } finally {
+            main.stop();
+        }
+    }
+
     @Test
     void testMcpAllowedOriginWildcardFailsFast() {
         Main main = new Main();
@@ -142,8 +179,12 @@ class McpServerMainPropertiesTest {
     }
 
     private static int initialize(int port, String origin) throws Exception {
+        return initialize(URI.create("http://localhost:"; + port + "/mcp"), 
origin);
+    }
+
+    private static int initialize(URI uri, String origin) throws Exception {
         HttpRequest.Builder builder = HttpRequest.newBuilder()
-                .uri(URI.create("http://localhost:"; + port + "/mcp"))
+                .uri(uri)
                 .timeout(Duration.ofSeconds(10))
                 .header("Content-Type", "application/json")
                 .header("Accept", "application/json, text/event-stream")
diff --git 
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpHostTest.java
 
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpHostTest.java
new file mode 100644
index 000000000000..dbdd1f9c5413
--- /dev/null
+++ 
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpHostTest.java
@@ -0,0 +1,331 @@
+/*
+ * 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.vertx;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.ai.tool.AiToolParameterHelper.ParameterDef;
+import org.apache.camel.component.mcp.server.McpServerInfo;
+import org.apache.camel.component.mcp.server.McpServerTool;
+import org.apache.camel.component.mcp.server.McpToolCallHandler;
+import org.apache.camel.component.mcp.server.McpToolCallResult;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies that the Vert.x streamable transport checks the {@code Host} 
header on every method of the MCP endpoint when
+ * allowed hosts are configured, and not at all otherwise (CAMEL-26665).
+ */
+class VertxMcpHostTest {
+
+    private static final HttpClient HTTP = HttpClient.newHttpClient();
+
+    private static final String INITIALIZE_REQUEST = """
+            
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18",
+            "capabilities":{},"clientInfo":{"name":"test","version":"1"}}}""";
+
+    private static final String INITIALIZED_NOTIFICATION = """
+            
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}""";
+
+    private static final String CALL_TOOL_REQUEST = """
+            
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"ping","arguments":{}}}""";
+
+    private CamelContext camelContext;
+    private VertxMcpServerEngine engine;
+    private int port;
+
+    @AfterEach
+    void stop() {
+        if (engine != null) {
+            engine.stop();
+        }
+        if (camelContext != null) {
+            camelContext.stop();
+        }
+    }
+
+    @Test
+    void hostIsNotCheckedByDefault() throws Exception {
+        start(null);
+
+        assertThat(post("localhost", null, 
INITIALIZE_REQUEST).statusCode()).isEqualTo(200);
+        assertThat(post("127.0.0.1", null, 
INITIALIZE_REQUEST).statusCode()).isEqualTo(200);
+        assertThat(rawPost("Host: attacker.example.com:" + 
port)).startsWith("HTTP/1.1 200");
+        assertThat(engine.sessionCount()).isEqualTo(3);
+    }
+
+    @Test
+    void allowedHostIsServed() throws Exception {
+        start(List.of("localhost:*"));
+
+        HttpResponse<String> response = post("localhost", null, 
INITIALIZE_REQUEST);
+
+        assertThat(response.statusCode()).isEqualTo(200);
+        
assertThat(response.headers().firstValue("mcp-session-id")).isPresent();
+        assertThat(engine.sessionCount()).isEqualTo(1);
+    }
+
+    @Test
+    void otherHostIsRejected() throws Exception {
+        start(List.of("localhost:*"));
+
+        HttpResponse<String> response = post("127.0.0.1", null, 
INITIALIZE_REQUEST);
+
+        assertThat(response.statusCode()).isEqualTo(421);
+        
assertThat(response.headers().firstValue("Content-Type")).hasValueSatisfying(
+                value -> assertThat(value).startsWith("application/json"));
+        assertThat(response.body())
+                .contains("\"jsonrpc\":\"2.0\"")
+                .contains("\"code\":-32600")
+                .contains("Host not allowed")
+                .doesNotContain("\"id\"");
+        assertThat(response.headers().firstValue("mcp-session-id")).isEmpty();
+        assertThat(engine.sessionCount()).isZero();
+    }
+
+    @Test
+    void reboundHostNameIsRejected() throws Exception {
+        start(List.of("localhost:*", "127.0.0.1:*"));
+
+        // a page on a host name that resolves to this server (DNS rebinding) 
sends its own host name
+        assertThat(rawPost("Host: attacker.example.com:" + 
port)).startsWith("HTTP/1.1 421");
+        assertThat(rawPost("Host: localhost.attacker.example.com:" + 
port)).startsWith("HTTP/1.1 421");
+        assertThat(engine.sessionCount()).isZero();
+    }
+
+    @Test
+    void missingHostIsRejected() throws Exception {
+        start(List.of("localhost:*"));
+
+        // Vert.x refuses an HTTP/1.1 request without a Host header itself; 
HTTP/1.0 has no Host header to require
+        assertThat(rawPost("HTTP/1.1", null)).startsWith("HTTP/1.1 400");
+        assertThat(rawPost("HTTP/1.0", null)).contains(" 421 ");
+        assertThat(engine.sessionCount()).isZero();
+    }
+
+    @Test
+    void hostIsComparedIgnoringCase() throws Exception {
+        start(List.of("mcp.example.com:*"));
+
+        assertThat(rawPost("Host: MCP.Example.COM:" + 
port)).startsWith("HTTP/1.1 200");
+    }
+
+    @Test
+    void wildcardPortAlsoMatchesAHostWithoutAPort() throws Exception {
+        start(List.of("mcp.example.com:*"));
+
+        assertThat(rawPost("Host: mcp.example.com")).startsWith("HTTP/1.1 
200");
+        assertThat(rawPost("Host: mcp.example.com:" + 
port)).startsWith("HTTP/1.1 200");
+    }
+
+    @Test
+    void exactPortMatchesThatPortOnly() throws Exception {
+        start(List.of("mcp.example.com:8443", "proxy.example.com"));
+
+        assertThat(rawPost("Host: mcp.example.com:8443")).startsWith("HTTP/1.1 
200");
+        assertThat(rawPost("Host: mcp.example.com:" + 
port)).startsWith("HTTP/1.1 421");
+        assertThat(rawPost("Host: mcp.example.com")).startsWith("HTTP/1.1 
421");
+        assertThat(rawPost("Host: proxy.example.com")).startsWith("HTTP/1.1 
200");
+        assertThat(rawPost("Host: proxy.example.com:" + 
port)).startsWith("HTTP/1.1 421");
+    }
+
+    @Test
+    void originIsStillCheckedFirst() throws Exception {
+        start(List.of("localhost:*"));
+
+        HttpResponse<String> response = post("localhost", 
"http://app.example.com";, null, INITIALIZE_REQUEST);
+
+        assertThat(response.statusCode()).isEqualTo(403);
+        assertThat(response.body()).contains("Origin not allowed");
+    }
+
+    @Test
+    void rejectedHostDoesNotCallTheTool() throws Exception {
+        start(List.of("localhost:*"));
+        AtomicInteger calls = new AtomicInteger();
+        engine.toolAdded(tool("ping", arguments -> {
+            calls.incrementAndGet();
+            return new McpToolCallResult("pong", false);
+        }));
+        String sessionId = initializeSession();
+
+        HttpResponse<String> rejected = post("127.0.0.1", sessionId, 
CALL_TOOL_REQUEST);
+        assertThat(rejected.statusCode()).isEqualTo(421);
+        assertThat(calls).hasValue(0);
+
+        HttpResponse<String> served = post("localhost", sessionId, 
CALL_TOOL_REQUEST);
+        assertThat(served.statusCode()).isEqualTo(200);
+        assertThat(served.body()).contains("pong");
+        assertThat(calls).hasValue(1);
+    }
+
+    @Test
+    void sseChannelChecksTheHost() throws Exception {
+        start(List.of("localhost:*"));
+        String sessionId = initializeSession();
+
+        HttpResponse<InputStream> rejected = HTTP.send(
+                request("127.0.0.1", null, sessionId).header("Accept", 
"text/event-stream").GET().build(),
+                HttpResponse.BodyHandlers.ofInputStream());
+        try (InputStream body = rejected.body()) {
+            assertThat(rejected.statusCode()).isEqualTo(421);
+        }
+    }
+
+    @Test
+    void deleteChecksTheHost() throws Exception {
+        start(List.of("localhost:*"));
+        String sessionId = initializeSession();
+
+        assertThat(delete("127.0.0.1", sessionId)).isEqualTo(421);
+        assertThat(engine.sessionCount()).isEqualTo(1);
+
+        assertThat(delete("localhost", sessionId)).isEqualTo(200);
+        assertThat(engine.sessionCount()).isZero();
+    }
+
+    private void start(List<String> allowedHosts) throws Exception {
+        port = AvailablePortFinder.getNextAvailable();
+        camelContext = new DefaultCamelContext();
+        MainHttpServer main = new MainHttpServer();
+        main.setCamelContext(camelContext);
+        main.setHost("0.0.0.0");
+        main.setPort(port);
+        camelContext.addService(main);
+        camelContext.start();
+
+        engine = new VertxMcpServerEngine();
+        engine.setCamelContext(camelContext);
+        engine.initialize(new McpServerInfo(
+                "host", "1.0", "/mcp", 0, 0, null, null, null, null, null, 
null, null, null, null, allowedHosts));
+        engine.start();
+    }
+
+    private String initializeSession() throws Exception {
+        HttpResponse<String> response = post("localhost", null, 
INITIALIZE_REQUEST);
+        assertThat(response.statusCode()).isEqualTo(200);
+        String sessionId = 
response.headers().firstValue("mcp-session-id").orElseThrow();
+        assertThat(post("localhost", sessionId, 
INITIALIZED_NOTIFICATION).statusCode()).isEqualTo(202);
+        return sessionId;
+    }
+
+    private HttpResponse<String> post(String host, String origin, String 
sessionId, String body) throws Exception {
+        HttpRequest.Builder builder = request(host, origin, sessionId)
+                .header("Content-Type", "application/json")
+                .header("Accept", "application/json, text/event-stream")
+                .POST(HttpRequest.BodyPublishers.ofString(body));
+        return HTTP.send(builder.build(), 
HttpResponse.BodyHandlers.ofString());
+    }
+
+    private HttpResponse<String> post(String host, String sessionId, String 
body) throws Exception {
+        return post(host, null, sessionId, body);
+    }
+
+    private int delete(String host, String sessionId) throws Exception {
+        HttpRequest.Builder builder = request(host, null, sessionId).DELETE();
+        return HTTP.send(builder.build(), 
HttpResponse.BodyHandlers.discarding()).statusCode();
+    }
+
+    /**
+     * A request to {@code host} on the port of the server: the HTTP client 
sends the host and port of the URI as the
+     * {@code Host} header, and both {@code localhost} and {@code 127.0.0.1} 
reach the server.
+     */
+    private HttpRequest.Builder request(String host, String origin, String 
sessionId) {
+        HttpRequest.Builder builder = HttpRequest.newBuilder()
+                .uri(URI.create("http://"; + host + ":" + port + "/mcp"))
+                .timeout(Duration.ofSeconds(10));
+        if (origin != null) {
+            builder.header("Origin", origin);
+        }
+        if (sessionId != null) {
+            builder.header("Mcp-Session-Id", sessionId);
+        }
+        return builder;
+    }
+
+    /**
+     * Sends an initialize request over a plain socket, with the given HTTP 
version and {@code Host} header line, or
+     * none when {@code null} (the HTTP client does not allow setting it), and 
returns the response.
+     */
+    private String rawPost(String hostLine) throws Exception {
+        return rawPost("HTTP/1.1", hostLine);
+    }
+
+    private String rawPost(String version, String hostLine) throws Exception {
+        byte[] body = INITIALIZE_REQUEST.getBytes(StandardCharsets.UTF_8);
+        String head = "POST /mcp " + version + "\r\n"
+                      + (hostLine != null ? hostLine + "\r\n" : "")
+                      + "Content-Type: application/json\r\n"
+                      + "Accept: application/json, text/event-stream\r\n"
+                      + "Content-Length: " + body.length + "\r\n"
+                      + "Connection: close\r\n\r\n";
+        try (Socket socket = new Socket("127.0.0.1", port)) {
+            socket.setSoTimeout(10_000);
+            OutputStream out = socket.getOutputStream();
+            out.write(head.getBytes(StandardCharsets.US_ASCII));
+            out.write(body);
+            out.flush();
+            return new String(socket.getInputStream().readAllBytes(), 
StandardCharsets.UTF_8);
+        }
+    }
+
+    private static McpServerTool tool(String name, McpToolCallHandler handler) 
{
+        return new McpServerTool() {
+            @Override
+            public String name() {
+                return name;
+            }
+
+            @Override
+            public String description() {
+                return "Answers pong";
+            }
+
+            @Override
+            public String inputSchemaJson() {
+                return null;
+            }
+
+            @Override
+            public Map<String, ParameterDef> parameters() {
+                return Map.of();
+            }
+
+            @Override
+            public McpToolCallHandler handler() {
+                return handler;
+            }
+        };
+    }
+}
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 402a5ffb8f06..47fd5067db27 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,6 +38,7 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         map.put("JwtKeystorePath", java.lang.String.class);
         map.put("JwtKeystoreType", java.lang.String.class);
         map.put("MaxBodySize", java.lang.Long.class);
+        map.put("McpAllowedHosts", java.lang.String.class);
         map.put("McpAllowedOrigins", java.lang.String.class);
         map.put("McpEnabled", boolean.class);
         map.put("McpInstructions", java.lang.String.class);
@@ -98,6 +99,8 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         case "jwtKeystoreType": 
target.setJwtKeystoreType(property(camelContext, java.lang.String.class, 
value)); return true;
         case "maxbodysize":
         case "maxBodySize": target.setMaxBodySize(property(camelContext, 
java.lang.Long.class, value)); return true;
+        case "mcpallowedhosts":
+        case "mcpAllowedHosts": 
target.setMcpAllowedHosts(property(camelContext, java.lang.String.class, 
value)); return true;
         case "mcpallowedorigins":
         case "mcpAllowedOrigins": 
target.setMcpAllowedOrigins(property(camelContext, java.lang.String.class, 
value)); return true;
         case "mcpenabled":
@@ -186,6 +189,8 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         case "jwtKeystoreType": return java.lang.String.class;
         case "maxbodysize":
         case "maxBodySize": return java.lang.Long.class;
+        case "mcpallowedhosts":
+        case "mcpAllowedHosts": return java.lang.String.class;
         case "mcpallowedorigins":
         case "mcpAllowedOrigins": return java.lang.String.class;
         case "mcpenabled":
@@ -270,6 +275,8 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         case "jwtKeystoreType": return target.getJwtKeystoreType();
         case "maxbodysize":
         case "maxBodySize": return target.getMaxBodySize();
+        case "mcpallowedhosts":
+        case "mcpAllowedHosts": return target.getMcpAllowedHosts();
         case "mcpallowedorigins":
         case "mcpAllowedOrigins": return target.getMcpAllowedOrigins();
         case "mcpenabled":
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 1b2d7a784d63..dfa566aca830 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
@@ -463,6 +463,7 @@
     { "name": "camel.server.jwtKeystorePath", "required": false, 
"description": "Path to the keystore file used for JWT tokens validation.", 
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", 
"type": "string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.server.jwtKeystoreType", "required": false, 
"description": "Type of the keystore used for JWT tokens validation (jks, 
pkcs12, etc.).", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "secret": false },
     { "name": "camel.server.maxBodySize", "required": false, "description": 
"Maximum HTTP body size the embedded HTTP server can accept.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "integer", 
"javaType": "java.lang.Long", "secret": false },
+    { "name": "camel.server.mcpAllowedHosts", "required": false, 
"description": "Comma-separated list of hosts allowed in the Host header of a 
request to the MCP endpoint on the Vert.x streamable transport, such as 
mcp.example.com or localhost:*. Each entry is a host, or a host and a port, 
where the port may be an asterisk to match any port (or none). The host is 
compared ignoring case. When set, a request whose Host header is missing or not 
in the list is rejected with HTTP 421 (Misdire [...]
     { "name": "camel.server.mcpAllowedOrigins", "required": false, 
"description": "Comma-separated list of origins allowed to call the MCP 
endpoint on the Vert.x streamable transport. Each entry is an exact origin 
(scheme, host and port), or ends with a colon and an asterisk to match any 
port. Scheme and host are compared ignoring case, and a default port (80 for 
http, 443 for https) may be left out. A wildcard for all origins is not 
supported: an entry that is not of the form scheme:\/\ [...]
     { "name": "camel.server.mcpEnabled", "required": false, "description": 
"Whether to expose ai-tool routes as MCP tools, ai-resource routes as MCP 
resources and ai-prompt routes as MCP prompts. Requires camel-mcp-server on the 
classpath. Use setMcpTransport(String) to choose streamable HTTP (default) or 
stdio for IDE subprocess integration. By default, the MCP server is not 
enabled.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType" [...]
     { "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 },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index ebbbbfb72fe8..0270e06c65c8 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -197,7 +197,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 40 options, which are listed below.
+The camel.server supports 41 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -217,6 +217,7 @@ The camel.server supports 40 options, which are listed 
below.
 | `camel.server.jwtKeystorePath` | Path to the keystore file used for JWT 
tokens validation. |  | String
 | `camel.server.jwtKeystoreType` | Type of the keystore used for JWT tokens 
validation (jks, pkcs12, etc.). |  | String
 | `camel.server.maxBodySize` | Maximum HTTP body size the embedded HTTP server 
can accept. |  | Long
+| `camel.server.mcpAllowedHosts` | Comma-separated list of hosts allowed in 
the Host header of a request to the MCP endpoint on the Vert.x streamable 
transport, such as mcp.example.com or localhost:*. Each entry is a host, or a 
host and a port, where the port may be an asterisk to match any port (or none). 
The host is compared ignoring case. When set, a request whose Host header is 
missing or not in the list is rejected with HTTP 421 (Misdirected Request), 
before its body is read and bef [...]
 | `camel.server.mcpAllowedOrigins` | Comma-separated list of origins allowed 
to call the MCP endpoint on the Vert.x streamable transport. Each entry is an 
exact origin (scheme, host and port), or ends with a colon and an asterisk to 
match any port. Scheme and host are compared ignoring case, and a default port 
(80 for http, 443 for https) may be left out. A wildcard for all origins is not 
supported: an entry that is not of the form scheme://host or scheme://host:port 
fails the startup. A [...]
 | `camel.server.mcpEnabled` | Whether to expose ai-tool routes as MCP tools, 
ai-resource routes as MCP resources and ai-prompt routes as MCP prompts. 
Requires camel-mcp-server on the classpath. Use setMcpTransport(String) to 
choose streamable HTTP (default) or stdio for IDE subprocess integration. By 
default, the MCP server is not enabled. | false | boolean
 | `camel.server.mcpInstructions` | Top-level MCP instructions returned to 
clients on initialize. |  | 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 124cb992cf44..54699cb39dd6 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
@@ -91,6 +91,8 @@ public class HttpServerConfigurationProperties implements 
BootstrapCloseable {
     private long mcpSessionIdleTtl = 300000;
     @Metadata
     private String mcpAllowedOrigins;
+    @Metadata
+    private String mcpAllowedHosts;
     @Metadata(label = "security")
     private String mcpOauthProfile;
     @Metadata(label = "security")
@@ -560,6 +562,23 @@ public class HttpServerConfigurationProperties implements 
BootstrapCloseable {
         this.mcpAllowedOrigins = mcpAllowedOrigins;
     }
 
+    public String getMcpAllowedHosts() {
+        return mcpAllowedHosts;
+    }
+
+    /**
+     * Comma-separated list of hosts allowed in the Host header of a request 
to the MCP endpoint on the Vert.x
+     * streamable transport, such as mcp.example.com or localhost:*. Each 
entry is a host, or a host and a port, where
+     * the port may be an asterisk to match any port (or none). The host is 
compared ignoring case. When set, a request
+     * whose Host header is missing or not in the list is rejected with HTTP 
421 (Misdirected Request), before its body
+     * is read and before any session is created; this protects a server on a 
private address against DNS rebinding. An
+     * entry with a scheme, a path or a wildcard for all hosts fails the 
startup. When not set, the Host header is not
+     * checked.
+     */
+    public void setMcpAllowedHosts(String mcpAllowedHosts) {
+        this.mcpAllowedHosts = mcpAllowedHosts;
+    }
+
     public String getMcpOauthProfile() {
         return mcpOauthProfile;
     }
@@ -913,6 +932,15 @@ public class HttpServerConfigurationProperties implements 
BootstrapCloseable {
         return this;
     }
 
+    /**
+     * Comma-separated list of hosts allowed in the Host header of a request 
to the MCP endpoint on the Vert.x
+     * streamable transport. When not set, the Host header is not checked.
+     */
+    public HttpServerConfigurationProperties withMcpAllowedHosts(String 
mcpAllowedHosts) {
+        this.mcpAllowedHosts = mcpAllowedHosts;
+        return this;
+    }
+
     /**
      * Name of the camel-oauth profile used to authenticate OAuth bearer 
tokens on the MCP HTTP endpoint.
      */

Reply via email to