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

FreeOnePlus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git


The following commit(s) were added to refs/heads/master by this push:
     new 95f1c24  fix: align compose ports and health probes (#132)
95f1c24 is described below

commit 95f1c24c25972a28fe263f0b82bd209c190ecc2d
Author: Yijia Su <[email protected]>
AuthorDate: Thu Jul 30 02:27:05 2026 +0800

    fix: align compose ports and health probes (#132)
---
 .dockerignore                            |  13 +++-
 Dockerfile                               |  19 +++--
 README.md                                |  25 ++++++-
 docker-compose.yml                       |  25 +++----
 doris_mcp_server/main.py                 |   8 ++-
 doris_mcp_server/multiworker_app.py      |   6 +-
 doris_mcp_server/protocol.py             |  46 +++++++++---
 doris_mcp_server/utils/config.py         |  20 ++++++
 requirements.txt                         |   3 +-
 start_server.sh                          |  17 +++--
 test/deployment/test_compose_contract.py | 119 +++++++++++++++++++++++++++++++
 test/protocol/test_mcp_v2_protocol.py    |  19 +++++
 test/protocol/test_multiworker_config.py |   7 ++
 13 files changed, 287 insertions(+), 40 deletions(-)

diff --git a/.dockerignore b/.dockerignore
index 9f335b5..a4e0162 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,11 @@
-**/.venv
-**/venv
\ No newline at end of file
+# Keep the build context limited to files copied by Dockerfile. In particular,
+# do not send source-control metadata, local operator files, tests, or reports
+# to the Docker daemon or a remote builder.
+**
+!.dockerignore
+!Dockerfile
+!LICENSE.txt
+!requirements.txt
+!start_server.sh
+!doris_mcp_server/
+!doris_mcp_server/**
diff --git a/Dockerfile b/Dockerfile
index 3a70519..c772ec6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -41,8 +41,11 @@ COPY requirements.txt .
 # Install Python dependencies
 RUN pip install --no-cache-dir -r requirements.txt
 
-# Copy application code
-COPY . .
+# Copy only runtime files. This keeps source-control metadata, local reports,
+# tests, caches, and untracked operator files out of the production image.
+COPY doris_mcp_server ./doris_mcp_server
+COPY start_server.sh .
+COPY LICENSE.txt .
 
 # Convert line endings for shell scripts and ensure proper execution format
 RUN find . -name "*.sh" -exec dos2unix {} \; && \
@@ -56,12 +59,14 @@ RUN groupadd -r doris && useradd -r -g doris doris
 RUN chown -R doris:doris /app
 USER doris
 
-# Health check
-HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
-    CMD curl -f http://localhost:3000/health || exit 1
+# The image-level probe reports process liveness. Deployments that route Doris
+# traffic should override it with the /ready probe, as docker-compose.yml does.
+HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
+    CMD curl --fail --silent --show-error --max-time 3 \
+        http://127.0.0.1:3000/live || exit 1
 
-# Expose ports
-EXPOSE 3000 3001 3002
+# Streamable HTTP, liveness, and readiness share the server's HTTP listener.
+EXPOSE 3000
 
 # Start command
 CMD ["/app/start_server.sh"]
diff --git a/README.md b/README.md
index edc2123..eeea7c2 100644
--- a/README.md
+++ b/README.md
@@ -379,8 +379,31 @@ If you want to run only Doris MCP Server in docker:
 ```bash
 cd doris-mcp-server
 docker build -t doris-mcp-server .
-docker run -d -p <port>:<port> -v /*your-host*/doris-mcp-server/.env:/app/.env 
--name <your-mcp-server-name> -it doris-mcp-server:latest
+docker run -d -p <host-port>:3000 -v 
/*your-host*/doris-mcp-server/.env:/app/.env --name <your-mcp-server-name> 
doris-mcp-server:latest
 ```
+
+The container always listens on port `3000`. The bundled Compose deployment
+publishes it on host port `3000` by default and publishes Grafana on host port
+`3003`, so the two services do not contend for the same port. Override those
+defaults with `MCP_HTTP_PORT` and `GRAFANA_HTTP_PORT`:
+
+```bash
+MCP_HTTP_PORT=3100 GRAFANA_HTTP_PORT=3103 docker compose up -d
+```
+
+The image-level Docker health check uses `/live`, so a temporary Doris outage
+does not cause the MCP process to be treated as dead. Compose overrides that
+probe with `/ready` and only marks the service ready after the bounded Doris
+probe succeeds. Both probes use the actual internal listener on port `3000`.
+
+Compose allows `127.0.0.1:*` and `localhost:*` Host headers by default. A
+deployment reached through another hostname, IP address, or reverse proxy must
+set an explicit comma-separated allowlist; an allow-all `*` value is rejected:
+
+```bash
+MCP_ALLOWED_HOSTS='mcp.example.com,mcp.example.com:*' docker compose up -d
+```
+
 **Service Endpoints:**
 
 *   **Streamable HTTP**: `http://<host>:<port>/mcp` (MCP messages use `POST`; 
do not depend on `GET` or `DELETE` compatibility behavior)
diff --git a/docker-compose.yml b/docker-compose.yml
index c9cbed6..9061ba1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,8 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-version: '3.8'
-
 services:
   # Doris MCP Server
   doris-mcp-server:
@@ -24,9 +22,7 @@ services:
       dockerfile: Dockerfile
     container_name: doris-mcp-server
     ports:
-      - "3000:3000"  # MCP service port
-      - "3001:3001"  # Monitoring metrics port
-      - "3002:3002"  # Health check port
+      - "${MCP_HTTP_PORT:-3000}:3000"  # Streamable HTTP, liveness, and 
readiness
     environment:
       # Database configuration
       - DORIS_HOST=doris-fe
@@ -40,7 +36,9 @@ services:
       - DORIS_MAX_CONNECTIONS=20
       
       # Security configuration
-      - MCP_HOST=0.0.0.0
+      - SERVER_HOST=0.0.0.0
+      - SERVER_PORT=3000
+      - MCP_ALLOWED_HOSTS=${MCP_ALLOWED_HOSTS:-127.0.0.1:*,localhost:*}
       - ENABLE_TOKEN_AUTH=true
       - TOKEN_ADMIN=${MCP_STATIC_TOKEN:?Set MCP_STATIC_TOKEN to a securely 
generated value of at least 32 characters}
       - MAX_RESULT_ROWS=10000
@@ -60,15 +58,17 @@ services:
       - ./logs:/app/logs
       - ./config:/app/config
     depends_on:
-      - doris-fe
-      - doris-be
+      doris-fe:
+        condition: service_healthy
+      doris-be:
+        condition: service_healthy
     networks:
       - doris-network
     restart: unless-stopped
     healthcheck:
-      test: ["CMD", "curl", "-f", "http://localhost:8082/health";]
+      test: ["CMD", "curl", "--fail", "--silent", "--show-error", 
"--max-time", "3", "http://127.0.0.1:3000/ready";]
       interval: 30s
-      timeout: 10s
+      timeout: 5s
       retries: 3
       start_period: 40s
 
@@ -165,7 +165,7 @@ services:
     image: grafana/grafana:latest
     container_name: doris-grafana
     ports:
-      - "3000:3000"
+      - "${GRAFANA_HTTP_PORT:-3003}:3000"
     environment:
       - GF_SECURITY_ADMIN_PASSWORD=admin123
     volumes:
@@ -190,7 +190,8 @@ services:
       - ./nginx/ssl:/etc/nginx/ssl
       - ./nginx/logs:/var/log/nginx
     depends_on:
-      - doris-mcp-server
+      doris-mcp-server:
+        condition: service_healthy
     networks:
       - doris-network
     restart: unless-stopped
diff --git a/doris_mcp_server/main.py b/doris_mcp_server/main.py
index 7102194..ed46ba6 100644
--- a/doris_mcp_server/main.py
+++ b/doris_mcp_server/main.py
@@ -67,6 +67,8 @@ def _multiworker_environment(
         "DORIS_DATABASE": config.database.database,
         "SERVER_HOST": host,
         "SERVER_PORT": str(port),
+        "MCP_ALLOWED_HOSTS": ",".join(config.mcp_allowed_hosts),
+        "MCP_ALLOWED_ORIGINS": ",".join(config.mcp_allowed_origins),
         "SERVER_NAME": config.server_name,
         "TRANSPORT": "http",
         "WORKERS": str(workers),
@@ -218,7 +220,11 @@ class DorisServer:
                 app=self.server,
                 json_response=True,
                 stateless=True,
-                security_settings=create_transport_security(host),
+                security_settings=create_transport_security(
+                    host,
+                    allowed_hosts=self.config.mcp_allowed_hosts,
+                    allowed_origins=self.config.mcp_allowed_origins,
+                ),
             )
             
             self.logger.info(f"StreamableHTTP session manager created, will 
start at http://{host}:{port}";)
diff --git a/doris_mcp_server/multiworker_app.py 
b/doris_mcp_server/multiworker_app.py
index 74d1335..cd45ae7 100644
--- a/doris_mcp_server/multiworker_app.py
+++ b/doris_mcp_server/multiworker_app.py
@@ -129,7 +129,11 @@ async def initialize_worker():
             app=_worker_server,
             json_response=True,
             stateless=True,
-            security_settings=create_transport_security(config.server_host),
+            security_settings=create_transport_security(
+                config.server_host,
+                allowed_hosts=config.mcp_allowed_hosts,
+                allowed_origins=config.mcp_allowed_origins,
+            ),
         )
         
         # Start the session manager context
diff --git a/doris_mcp_server/protocol.py b/doris_mcp_server/protocol.py
index 0790d1d..5194db9 100644
--- a/doris_mcp_server/protocol.py
+++ b/doris_mcp_server/protocol.py
@@ -20,7 +20,7 @@ from __future__ import annotations
 
 import json
 import logging
-from collections.abc import Mapping
+from collections.abc import Iterable, Mapping
 from typing import Any, Protocol
 
 from mcp.server import Server, ServerRequestContext
@@ -81,24 +81,52 @@ class PromptsManager(Protocol):
     ) -> GetPromptResult: ...
 
 
-def create_transport_security(host: str) -> TransportSecuritySettings:
+def _explicit_transport_allowlist(
+    values: Iterable[str] | None,
+    *,
+    setting: str,
+) -> list[str]:
+    """Normalize an explicit transport allowlist without accepting 
allow-all."""
+    normalized = list(dict.fromkeys(str(value).strip() for value in values or 
()))
+    normalized = [value for value in normalized if value]
+    if "*" in normalized:
+        raise ValueError(
+            f"{setting} must list deployment hosts explicitly; '*' is not 
allowed"
+        )
+    return normalized
+
+
+def create_transport_security(
+    host: str,
+    *,
+    allowed_hosts: Iterable[str] | None = None,
+    allowed_origins: Iterable[str] | None = None,
+) -> TransportSecuritySettings:
     """Create a fail-closed Host and Origin policy for a bind host."""
+    configured_hosts = _explicit_transport_allowlist(
+        allowed_hosts,
+        setting="MCP_ALLOWED_HOSTS",
+    )
+    configured_origins = _explicit_transport_allowlist(
+        allowed_origins,
+        setting="MCP_ALLOWED_ORIGINS",
+    )
     if host in {"127.0.0.1", "localhost", "::1"}:
-        allowed_hosts = ["127.0.0.1:*", "localhost:*", "[::1]:*"]
-        allowed_origins = [
+        default_hosts = ["127.0.0.1:*", "localhost:*", "[::1]:*"]
+        default_origins = [
             "http://127.0.0.1:*";,
             "http://localhost:*";,
             "http://[::1]:*";,
         ]
     else:
         # A bind address is not a public deployment hostname. Explicit
-        # deployment allowlists will replace this conservative fallback.
-        allowed_hosts = [host, f"{host}:*"]
-        allowed_origins = []
+        # deployment allowlists replace this conservative fallback.
+        default_hosts = [host, f"{host}:*"]
+        default_origins = []
     return TransportSecuritySettings(
         enable_dns_rebinding_protection=True,
-        allowed_hosts=allowed_hosts,
-        allowed_origins=allowed_origins,
+        allowed_hosts=configured_hosts or default_hosts,
+        allowed_origins=configured_origins or default_origins,
     )
 
 
diff --git a/doris_mcp_server/utils/config.py b/doris_mcp_server/utils/config.py
index 1adbef7..da2c5ed 100644
--- a/doris_mcp_server/utils/config.py
+++ b/doris_mcp_server/utils/config.py
@@ -604,6 +604,8 @@ class DorisConfig:
     server_version: str = field(default=__version__, init=False)
     server_host: str = "localhost"
     server_port: int = 3000
+    mcp_allowed_hosts: list[str] = field(default_factory=list)
+    mcp_allowed_origins: list[str] = field(default_factory=list)
     transport: str = "stdio"
     
     # Temporary files configuration
@@ -1122,6 +1124,20 @@ class DorisConfig:
         server_port = os.getenv("SERVER_PORT", "").strip()
         if server_port and server_port.isdigit():
             config.server_port = int(server_port)
+        if "MCP_ALLOWED_HOSTS" in os.environ:
+            config.mcp_allowed_hosts = [
+                value.strip()
+                for value in os.getenv("MCP_ALLOWED_HOSTS", "").split(",")
+                if value.strip()
+            ]
+            _mark_source(config, "mcp_allowed_hosts", "env")
+        if "MCP_ALLOWED_ORIGINS" in os.environ:
+            config.mcp_allowed_origins = [
+                value.strip()
+                for value in os.getenv("MCP_ALLOWED_ORIGINS", "").split(",")
+                if value.strip()
+            ]
+            _mark_source(config, "mcp_allowed_origins", "env")
         config.temp_files_dir = os.getenv("TEMP_FILES_DIR", 
config.temp_files_dir)
 
         return config
@@ -1136,6 +1152,8 @@ class DorisConfig:
             "server_name",
             "server_host",
             "server_port",
+            "mcp_allowed_hosts",
+            "mcp_allowed_origins",
             "temp_files_dir",
             "transport",
             "workers",
@@ -1207,6 +1225,8 @@ class DorisConfig:
             "server_version": self.server_version,
             "server_host": self.server_host,
             "server_port": self.server_port,
+            "mcp_allowed_hosts": self.mcp_allowed_hosts,
+            "mcp_allowed_origins": self.mcp_allowed_origins,
             "temp_files_dir": self.temp_files_dir,
             "database": {
                 "host": self.database.host,
diff --git a/requirements.txt b/requirements.txt
index 347f62d..670907c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
 # Do not edit this file manually, use 'python generate_requirements.py' to 
regenerate
 
 # === Core Dependencies ===
-mcp>=1.8.0,<2.0.0
+mcp>=2.0.0,<2.1.0
 aiomysql>=0.2.0
 PyMySQL>=1.1.0
 adbc-driver-manager>=0.8.0
@@ -12,6 +12,7 @@ asyncio-mqtt>=0.16.0
 aiofiles>=23.0.0
 aiohttp>=3.9.0
 aioredis>=2.0.0
+filelock>=3.16.1,<4.0.0
 pandas>=2.0.0
 numpy>=1.24.0
 python-dateutil>=2.8.0
diff --git a/start_server.sh b/start_server.sh
index 0282650..6002233 100755
--- a/start_server.sh
+++ b/start_server.sh
@@ -66,7 +66,10 @@ fi
 # Set HTTP-specific environment variables
 # FIX for Issue #62 Bug 4: Use SERVER_PORT instead of MCP_PORT for consistency 
with code
 export MCP_TRANSPORT_TYPE="http"
-export MCP_HOST="${MCP_HOST:-127.0.0.1}"
+export SERVER_HOST="${SERVER_HOST:-${MCP_HOST:-127.0.0.1}}"
+# Keep MCP_HOST available for existing .env files while using the server's
+# canonical host setting for the actual process.
+export MCP_HOST="${SERVER_HOST}"
 export SERVER_PORT="${SERVER_PORT:-3000}"  # Changed from MCP_PORT to 
SERVER_PORT
 export WORKERS="${WORKERS:-1}"
 export ALLOWED_ORIGINS="${ALLOWED_ORIGINS:-*}"
@@ -78,15 +81,16 @@ export MCP_DEBUG_ADAPTER="true"
 export PYTHONPATH="$(pwd):$PYTHONPATH"
 
 echo -e "${GREEN}Starting MCP server (Streamable HTTP mode)...${NC}"
-echo -e "${YELLOW}Service will run on 
http://${MCP_HOST}:${SERVER_PORT}/mcp${NC}";
-echo -e "${YELLOW}Health Check: http://${MCP_HOST}:${SERVER_PORT}/health${NC}";
-echo -e "${YELLOW}MCP Endpoint: http://${MCP_HOST}:${SERVER_PORT}/mcp${NC}";
+echo -e "${YELLOW}Service will run on 
http://${SERVER_HOST}:${SERVER_PORT}/mcp${NC}";
+echo -e "${YELLOW}Liveness: http://${SERVER_HOST}:${SERVER_PORT}/live${NC}";
+echo -e "${YELLOW}Readiness: http://${SERVER_HOST}:${SERVER_PORT}/ready${NC}";
+echo -e "${YELLOW}MCP Endpoint: http://${SERVER_HOST}:${SERVER_PORT}/mcp${NC}";
 echo -e "${YELLOW}Local access: http://localhost:${SERVER_PORT}/mcp${NC}";
 echo -e "${YELLOW}Workers: ${WORKERS}${NC}"
 echo -e "${YELLOW}Use Ctrl+C to stop the service${NC}"
 
 # Start the server in HTTP mode (Streamable HTTP)
-python -m doris_mcp_server.main --transport http --host ${MCP_HOST} --port 
${SERVER_PORT} --workers ${WORKERS}
+python -m doris_mcp_server.main --transport http --host "${SERVER_HOST}" 
--port "${SERVER_PORT}" --workers "${WORKERS}"
 
 # Check exit status
 if [ $? -ne 0 ]; then
@@ -98,4 +102,5 @@ fi
 echo -e "${YELLOW}Tip: If the page displays abnormally, please clear your 
browser cache or use incognito mode${NC}"
 echo -e "${YELLOW}Chrome browser clear cache shortcut: Ctrl+Shift+Del 
(Windows) or Cmd+Shift+Del (Mac)${NC}"
 echo -e "${CYAN}For testing HTTP endpoints, you can use:${NC}"
-echo -e "${CYAN}  curl http://127.0.0.1:${SERVER_PORT}/health${NC}";
+echo -e "${CYAN}  curl --fail http://127.0.0.1:${SERVER_PORT}/live${NC}";
+echo -e "${CYAN}  curl --fail http://127.0.0.1:${SERVER_PORT}/ready${NC}";
diff --git a/test/deployment/test_compose_contract.py 
b/test/deployment/test_compose_contract.py
new file mode 100644
index 0000000..a6b3e23
--- /dev/null
+++ b/test/deployment/test_compose_contract.py
@@ -0,0 +1,119 @@
+# 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.
+
+import re
+from pathlib import Path
+
+import yaml
+
+REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
+COMPOSE_PATH = REPOSITORY_ROOT / "docker-compose.yml"
+DOCKERFILE_PATH = REPOSITORY_ROOT / "Dockerfile"
+DOCKERIGNORE_PATH = REPOSITORY_ROOT / ".dockerignore"
+START_SCRIPT_PATH = REPOSITORY_ROOT / "start_server.sh"
+REQUIREMENTS_PATH = REPOSITORY_ROOT / "requirements.txt"
+DEFAULT_VARIABLE = re.compile(r"^\$\{[^}:]+:-([^}]+)\}$")
+
+
+def _compose() -> dict:
+    return yaml.safe_load(COMPOSE_PATH.read_text(encoding="utf-8"))
+
+
+def _default_published_port(port: str | dict) -> int:
+    published = str(
+        port["published"] if isinstance(port, dict) else port.rsplit(":", 1)[0]
+    )
+    match = DEFAULT_VARIABLE.fullmatch(published)
+    return int(match.group(1) if match else published)
+
+
+def test_default_compose_host_ports_are_unique() -> None:
+    compose = _compose()
+    published: list[int] = []
+    for service in compose["services"].values():
+        published.extend(
+            _default_published_port(port) for port in service.get("ports", [])
+        )
+
+    assert len(published) == len(set(published))
+
+
+def test_mcp_service_uses_one_real_listener_and_readiness_probe() -> None:
+    service = _compose()["services"]["doris-mcp-server"]
+
+    assert service["ports"] == ["${MCP_HTTP_PORT:-3000}:3000"]
+    assert "SERVER_HOST=0.0.0.0" in service["environment"]
+    assert "SERVER_PORT=3000" in service["environment"]
+    assert (
+        "MCP_ALLOWED_HOSTS=${MCP_ALLOWED_HOSTS:-127.0.0.1:*,localhost:*}"
+        in service["environment"]
+    )
+    assert service["healthcheck"]["test"] == [
+        "CMD",
+        "curl",
+        "--fail",
+        "--silent",
+        "--show-error",
+        "--max-time",
+        "3",
+        "http://127.0.0.1:3000/ready";,
+    ]
+    assert service["depends_on"]["doris-fe"]["condition"] == "service_healthy"
+    assert service["depends_on"]["doris-be"]["condition"] == "service_healthy"
+
+
+def test_grafana_default_port_does_not_conflict_with_mcp() -> None:
+    grafana = _compose()["services"]["grafana"]
+
+    assert grafana["ports"] == ["${GRAFANA_HTTP_PORT:-3003}:3000"]
+
+
+def test_image_healthcheck_uses_liveness_on_the_real_listener() -> None:
+    dockerfile = DOCKERFILE_PATH.read_text(encoding="utf-8")
+
+    assert "http://127.0.0.1:3000/live"; in dockerfile
+    assert "EXPOSE 3000\n" in dockerfile
+    assert "EXPOSE 3000 3001 3002" not in dockerfile
+    assert "COPY . ." not in dockerfile
+    assert "COPY doris_mcp_server ./doris_mcp_server" in dockerfile
+    assert "COPY start_server.sh ." in dockerfile
+
+
+def test_container_dependency_manifest_uses_mcp_sdk_v2() -> None:
+    requirements = REQUIREMENTS_PATH.read_text(encoding="utf-8").splitlines()
+
+    assert "mcp>=2.0.0,<2.1.0" in requirements
+    assert "mcp>=1.8.0,<2.0.0" not in requirements
+
+
+def test_docker_build_context_is_an_explicit_runtime_allowlist() -> None:
+    dockerignore = DOCKERIGNORE_PATH.read_text(encoding="utf-8").splitlines()
+
+    assert "**" in dockerignore
+    assert "!doris_mcp_server/**" in dockerignore
+    assert "!requirements.txt" in dockerignore
+    assert "!start_server.sh" in dockerignore
+    assert "!LICENSE.txt" in dockerignore
+
+
+def test_start_script_uses_canonical_server_host_and_documents_both_probes() 
-> None:
+    script = START_SCRIPT_PATH.read_text(encoding="utf-8")
+
+    assert 'SERVER_HOST="${SERVER_HOST:-${MCP_HOST:-127.0.0.1}}"' in script
+    assert '--host "${SERVER_HOST}" --port "${SERVER_PORT}"' in script
+    assert "/live" in script
+    assert "/ready" in script
diff --git a/test/protocol/test_mcp_v2_protocol.py 
b/test/protocol/test_mcp_v2_protocol.py
index 072d8d8..813ed4f 100644
--- a/test/protocol/test_mcp_v2_protocol.py
+++ b/test/protocol/test_mcp_v2_protocol.py
@@ -46,6 +46,25 @@ from test.protocol.stdio_capability_server import 
OneToolManager as ProfileToolM
 REQUIRED_EXTENSION = "io.apache.doris/read"
 
 
+def test_transport_security_accepts_explicit_deployment_allowlists():
+    settings = create_transport_security(
+        "0.0.0.0",
+        allowed_hosts=["mcp.example.test", "mcp.example.test:*"],
+        allowed_origins=["https://client.example.test";],
+    )
+
+    assert settings.allowed_hosts == [
+        "mcp.example.test",
+        "mcp.example.test:*",
+    ]
+    assert settings.allowed_origins == ["https://client.example.test";]
+
+
+def test_transport_security_rejects_allow_all_host():
+    with pytest.raises(ValueError, match="must list deployment hosts 
explicitly"):
+        create_transport_security("0.0.0.0", allowed_hosts=["*"])
+
+
 async def _unused_sampling_callback(context, params):
     del context, params
     raise AssertionError("The capability fixture must not issue sampling 
requests")
diff --git a/test/protocol/test_multiworker_config.py 
b/test/protocol/test_multiworker_config.py
index cccd204..9bf3cd4 100644
--- a/test/protocol/test_multiworker_config.py
+++ b/test/protocol/test_multiworker_config.py
@@ -23,6 +23,8 @@ def 
test_multiworker_environment_preserves_resolved_parent_config(monkeypatch):
     config.database.password = "test-password"
     config.database.database = "hhm_dt_sim"
     config.server_name = "doris-mcp-server"
+    config.mcp_allowed_hosts = ["mcp.example.test", "mcp.example.test:*"]
+    config.mcp_allowed_origins = ["https://client.example.test";]
 
     worker_env = _multiworker_environment(
         config,
@@ -43,6 +45,11 @@ def 
test_multiworker_environment_preserves_resolved_parent_config(monkeypatch):
     assert child_config.database.database == "hhm_dt_sim"
     assert child_config.server_host == "127.0.0.1"
     assert child_config.server_port == 31133
+    assert child_config.mcp_allowed_hosts == [
+        "mcp.example.test",
+        "mcp.example.test:*",
+    ]
+    assert child_config.mcp_allowed_origins == ["https://client.example.test";]
     assert child_config.server_name == "doris-mcp-server"
     assert child_config.server_version == __version__
     assert child_config.transport == "http"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to