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

FreeOnePlus pushed a commit to branch fix/flat-manifest-version-scope
in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git

commit dc365745f096ee3cb7be1c4d0121e78433409429
Author: FreeOnePlus <[email protected]>
AuthorDate: Fri Jul 31 22:11:25 2026 +0800

    fix: bound Flat-mode capability manifests
---
 CHANGELOG.md                                      |  3 ++
 doris_mcp_server/tools/capability_registry.py     | 28 +++++++++++---
 test/integration/test_real_doris_transports.py    | 39 +++++++++++++++++++
 test/tools/test_capability_registry.py            | 46 +++++++++++++++++++++++
 test/tools/test_capability_runtime_integration.py |  2 -
 5 files changed, 111 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index df7693e..c8ce2a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -152,6 +152,9 @@ under **Unreleased** until a new version is selected and 
published.
 - Excluded explicitly dead Doris components from active version gating while
   preserving them in node inventory, and kept live runtime manifests within
   the 16 KiB domain budget.
+- Limited public detected-version evidence to each child's actual Doris
+  component scope, preventing real Cluster manifests from overflowing the
+  16 KiB budget and breaking Flat-mode `tools/list`.
 - Preserved service availability after malformed requests, unknown methods,
   header mismatches, unsupported versions, and missing capabilities.
 - Kept static-token Doris pools usable after repeated query timeouts, and
diff --git a/doris_mcp_server/tools/capability_registry.py 
b/doris_mcp_server/tools/capability_registry.py
index 7e1b09a..6071530 100644
--- a/doris_mcp_server/tools/capability_registry.py
+++ b/doris_mcp_server/tools/capability_registry.py
@@ -235,7 +235,11 @@ class CapabilityEvaluator:
         child: ChildToolDefinition,
         auth_context: Any | None,
     ) -> Availability:
-        detected_versions = _detected_versions(snapshot)
+        feature = self._matrix.get_feature(domain.name, child.name)
+        detected_versions = _detected_versions(
+            snapshot,
+            feature.version_scope,
+        )
         if not self._bound_handlers.is_bound(domain.name, child.name):
             return Availability(
                 status=AvailabilityStatus.UNKNOWN,
@@ -248,7 +252,6 @@ class CapabilityEvaluator:
                 ),
             )
 
-        feature = self._matrix.get_feature(domain.name, child.name)
         if (
             snapshot.mixed_versions
             and feature.version_scope
@@ -890,6 +893,7 @@ def _unknown_snapshot(
 
 def _detected_versions(
     snapshot: DorisCapabilitySnapshot,
+    scope: CapabilityVersionScope,
 ) -> dict[str, tuple[str, ...]]:
     def values(versions: tuple[Any, ...]) -> tuple[str, ...]:
         return tuple(
@@ -904,11 +908,25 @@ def _detected_versions(
     master = values((snapshot.version_vector.master_fe,))
     followers = values(snapshot.version_vector.follower_fes)
     backends = values(snapshot.version_vector.backends)
-    if master:
+    include_master = scope in {
+        CapabilityVersionScope.MASTER_FE,
+        CapabilityVersionScope.ALL_FE,
+        CapabilityVersionScope.ALL_COMPONENTS,
+        CapabilityVersionScope.PROVIDER_WITH_MASTER_FE_BASELINE,
+    }
+    include_followers = scope in {
+        CapabilityVersionScope.ALL_FE,
+        CapabilityVersionScope.ALL_COMPONENTS,
+    }
+    include_backends = scope in {
+        CapabilityVersionScope.ALL_BE,
+        CapabilityVersionScope.ALL_COMPONENTS,
+    }
+    if include_master and master:
         detected["master_fe"] = master
-    if followers:
+    if include_followers and followers:
         detected["follower_fe"] = followers
-    if backends:
+    if include_backends and backends:
         detected["be"] = backends
     return detected
 
diff --git a/test/integration/test_real_doris_transports.py 
b/test/integration/test_real_doris_transports.py
index eb20e44..6037279 100644
--- a/test/integration/test_real_doris_transports.py
+++ b/test/integration/test_real_doris_transports.py
@@ -1262,6 +1262,45 @@ async def test_real_doris_tool_regression_paths(
         assert recovered_payload["data"][0]["recovered"] == 1
 
 
[email protected]("transport", ["http", "stdio"])
+async def test_real_doris_flat_tool_list_stays_bounded_and_callable(
+    transport: str,
+) -> None:
+    settings = _real_doris_settings()
+    environment = _server_environment(
+        settings,
+        user=settings.user,
+        password=settings.password,
+    )
+    environment["MCP_TOOL_EXPOSURE_MODE"] = "flat"
+
+    async with _transport_client(
+        transport,
+        environment,
+        read_timeout_seconds=60,
+    ) as client:
+        tools = {
+            tool.name: tool
+            for tool in (await client.list_tools(cache_mode="bypass")).tools
+        }
+        assert len(tools) == 47
+        assert "doris_query_execute_query" in tools
+
+        result = await client.call_tool(
+            "doris_query_execute_query",
+            {
+                "sql": "SELECT @@version_comment",
+                "max_rows": 1,
+            },
+        )
+        assert result.is_error is False
+        assert isinstance(result.structured_content, dict)
+        assert "4.0.5-rc01" in json.dumps(
+            result.structured_content,
+            ensure_ascii=False,
+        )
+
+
 @pytest.mark.skipif(
     os.getenv("DORIS_REAL_HTTP_INTEGRATION") != "1",
     reason="set DORIS_REAL_HTTP_INTEGRATION=1 with independent FE/BE HTTP 
endpoints",
diff --git a/test/tools/test_capability_registry.py 
b/test/tools/test_capability_registry.py
index f807089..c373975 100644
--- a/test/tools/test_capability_registry.py
+++ b/test/tools/test_capability_registry.py
@@ -196,6 +196,52 @@ def 
test_evaluator_requires_version_probes_handler_and_call_permission() -> None
     assert allowed.callable is True
 
 
[email protected](
+    ("domain_name", "child_name", "expected_components"),
+    (
+        ("doris_governance", "list_udfs", {"master_fe"}),
+        (
+            "doris_governance",
+            "get_lineage_capability_status",
+            {"master_fe", "follower_fe"},
+        ),
+        ("doris_cluster", "get_cache_status", {"be"}),
+        (
+            "doris_cluster",
+            "get_cluster_overview",
+            {"master_fe", "follower_fe", "be"},
+        ),
+        ("doris_query", "get_adbc_connection_info", {"master_fe"}),
+    ),
+)
+def test_evaluator_exposes_only_versions_relevant_to_feature_scope(
+    domain_name: str,
+    child_name: str,
+    expected_components: set[str],
+) -> None:
+    evaluator = CapabilityEvaluator(
+        matrix=DORIS_FEATURE_MATRIX,
+        bound_handlers=_BoundHandlers(
+            f"{domain_name}.{child_name}"
+        ),  # type: ignore[arg-type]
+    )
+    domain = DORIS_DOMAIN_CATALOG.resolve_domain(domain_name)
+    child = DORIS_DOMAIN_CATALOG.resolve_child(
+        domain_name,
+        child_name,
+    )
+
+    availability = evaluator.evaluate(
+        snapshot=_snapshot(),
+        providers=CapabilityProviderRegistry({}).snapshot(),
+        domain=domain,
+        child=child,
+        auth_context=None,
+    )
+
+    assert set(availability.detected_versions) == expected_components
+
+
 def test_compaction_prefers_native_tracker_and_uses_legacy_on_405() -> None:
     evaluator = CapabilityEvaluator(
         matrix=DORIS_FEATURE_MATRIX,
diff --git a/test/tools/test_capability_runtime_integration.py 
b/test/tools/test_capability_runtime_integration.py
index 1285275..4bbb741 100644
--- a/test/tools/test_capability_runtime_integration.py
+++ b/test/tools/test_capability_runtime_integration.py
@@ -140,7 +140,6 @@ async def 
test_default_manager_detects_caches_and_dispatches() -> None:
     assert execute_child.availability.callable is True
     assert execute_child.availability.detected_versions == {
         "master_fe": ("4.0.5",),
-        "be": ("4.0.5",),
     }
     assert first.manifest_version == second.manifest_version
     assert result.mode == "result"
@@ -151,4 +150,3 @@ async def 
test_default_manager_detects_caches_and_dispatches() -> None:
         "EXPLAIN SELECT 1"
     ) == 1
     manager._exec_query_tool.assert_awaited_once()
-


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

Reply via email to