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]
