This is an automated email from the ASF dual-hosted git repository. FreeOnePlus pushed a commit to branch feat/v1-15-admin-domain-reservation in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
commit bf13c0b9305b007857f57befab1e3f2dfd3bf400 Author: FreeOnePlus <[email protected]> AuthorDate: Fri Jul 31 21:45:22 2026 +0800 feat: reserve Doris administration domain --- .env.example | 6 + CHANGELOG.md | 5 + README.md | 28 ++++ doris_mcp_server/main.py | 6 + doris_mcp_server/tools/admin_domain.py | 186 ++++++++++++++++++++++++++ doris_mcp_server/utils/config.py | 47 +++++++ test/tools/test_admin_domain.py | 229 +++++++++++++++++++++++++++++++++ 7 files changed, 507 insertions(+) diff --git a/.env.example b/.env.example index c2b6e6f..e4c9338 100644 --- a/.env.example +++ b/.env.example @@ -79,6 +79,12 @@ DORIS_MAX_CONNECTION_AGE=3600 MCP_TOOL_PROVIDERS= MCP_TOOL_EXPOSURE_MODE=hierarchical +# Doris-changing MCP actions are reserved for a separately reviewed release. +# The 1.0 server rejects attempts to enable this domain or disable its future +# confirmation requirement; no administration tool or handler is registered. +MCP_ADMIN_DOMAIN_ENABLED=false +MCP_ADMIN_REQUIRE_CONFIRMATION=true + # Route-private capability snapshots drive each child availability decision. # A failed refresh may reuse the previous snapshot only during the bounded # stale grace window; unavailable or unknown capabilities remain fail-closed. diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ce2a0..09c53fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,9 @@ under **Unreleased** until a new version is selected and published. - An experimental read-only Apache Ossie Core semantic-grounding domain with four capability-gated children, revisioned model-summary resources, and explicit server-private Doris binding manifests. +- A fail-closed `doris_admin` architecture reservation that defines future + high-risk action, scope, preview/execute, confirmation, idempotency, and + rollback contracts without registering any management capability. - Real Doris process tests covering Streamable HTTP and stdio. ### Changed @@ -117,6 +120,8 @@ under **Unreleased** until a new version is selected and published. expressions. - Aligned semantic OAuth discovery, execution, resource exposure, and Doris OAuth scope issuance on explicit channel opt-in plus `semantic:read`. +- Rejected 1.0 configuration attempts to enable the reserved administration + domain or disable its mandatory confirmation invariant. ### Fixed diff --git a/README.md b/README.md index bfbd110..f1ca67c 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,11 @@ cp .env.example .env eight domain tools with progressive child discovery; `flat` returns the same 47 children under exact collision-free formal names (default: hierarchical) + * `MCP_ADMIN_DOMAIN_ENABLED`: Reserved administration-domain switch. + It must remain `false` in 1.0; setting it to `true` fails startup + validation + * `MCP_ADMIN_REQUIRE_CONFIRMATION`: Future administration confirmation + invariant. It must remain `true`; disabling it fails startup validation * `MCP_TOOL_PROVIDERS`: Comma-separated allowlist of installed `doris_mcp_server.tool_providers` entry points (default: empty) * `CAPABILITY_SNAPSHOT_TTL_SECONDS`: Lifetime of a private route-specific @@ -465,6 +470,29 @@ discovery grant are omitted, while authorized but unavailable children remain visible with `callable=false`. Doris RBAC remains the final data authorization backend for all Doris object access. +#### Administration domain reservation + +The `doris_admin` name and its future discovery, preview, execute, +confirmation, idempotency, and rollback contracts are reserved, but 1.0 +registers no administration domain, child, handler, resource, prompt, or write +operation. Neither hierarchical nor flat exposure includes an administration +tool, and a guessed `doris_admin` name receives the normal `Tool not found` +result. + +The reservation is deliberately fail-closed: + +```bash +MCP_ADMIN_DOMAIN_ENABLED=false +MCP_ADMIN_REQUIRE_CONFIRMATION=true +``` + +Changing the first value to `true`, or the second to `false`, makes +configuration validation fail. A future release must separately review every +action's exact scope, preview and execute handlers, explicit single-use +confirmation, argument digest, idempotency key, rollback behavior, Doris +authorization, and real write-path tests before it can register any +administration capability. + #### Apache Ossie semantic grounding (experimental) The optional `doris_semantic` domain reads the Apache Ossie Core semantic model diff --git a/doris_mcp_server/main.py b/doris_mcp_server/main.py index 76bf12c..5b9a93e 100644 --- a/doris_mcp_server/main.py +++ b/doris_mcp_server/main.py @@ -92,6 +92,12 @@ def _multiworker_environment( "MCP_LIST_PAGE_SIZE": str(config.mcp_list_page_size), "MCP_TOOL_PROVIDERS": ",".join(config.mcp_tool_providers), "MCP_TOOL_EXPOSURE_MODE": config.tool_exposure.mode, + "MCP_ADMIN_DOMAIN_ENABLED": str( + config.administration.enabled + ).lower(), + "MCP_ADMIN_REQUIRE_CONFIRMATION": str( + config.administration.require_confirmation + ).lower(), "CAPABILITY_SNAPSHOT_TTL_SECONDS": str( config.capability.snapshot_ttl_seconds ), diff --git a/doris_mcp_server/tools/admin_domain.py b/doris_mcp_server/tools/admin_domain.py new file mode 100644 index 0000000..5ea41b8 --- /dev/null +++ b/doris_mcp_server/tools/admin_domain.py @@ -0,0 +1,186 @@ +# 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. + +"""Fail-closed contracts reserved for a future Doris administration domain.""" + +from __future__ import annotations + +from enum import StrEnum +from typing import Annotated, Any, Literal, Self + +from pydantic import Field, model_validator + +from .domain_models import ( + BindingName, + ContractModel, + Identifier, + NonEmptyText, + ToolContractAnnotations, +) + +ADMIN_DOMAIN_NAME = "doris_admin" +ADMIN_DOMAIN_DISCOVERY_SCOPE = "domain:discover:doris_admin" +ADMIN_ACTION_ANNOTATIONS = ToolContractAnnotations( + read_only=False, + idempotent=False, + destructive=True, + open_world=False, + requires_confirmation=True, +) + + +class AdminRiskLevel(StrEnum): + """Allowed risk classifications for a future administrative action.""" + + HIGH = "high" + CRITICAL = "critical" + + +class AdminConfirmationContract(ContractModel): + """Single-use confirmation bound to a preview and exact arguments.""" + + required: Literal[True] = True + method: Literal["explicit_token"] = "explicit_token" + token_field: Identifier = "confirmation_token" + preview_revision_field: Identifier = "preview_revision" + argument_digest_algorithm: Literal["sha256"] = "sha256" + single_use: Literal[True] = True + expires_seconds: Annotated[int, Field(ge=30, le=900)] = 300 + + +class AdminActionContract(ContractModel): + """Future preview/execute boundary; this release registers no actions.""" + + name: Identifier + title: NonEmptyText + description: NonEmptyText + risk_level: AdminRiskLevel + preview_scope: BindingName + execute_scope: BindingName + preview_handler_name: BindingName + execute_handler_name: BindingName + confirmation: AdminConfirmationContract = Field( + default_factory=AdminConfirmationContract + ) + idempotency_key_field: Identifier = "idempotency_key" + rollback_supported: bool = False + rollback_scope: BindingName | None = None + rollback_handler_name: BindingName | None = None + annotations: ToolContractAnnotations = ADMIN_ACTION_ANNOTATIONS + + @model_validator(mode="after") + def _validate_action_boundary(self) -> Self: + expected_values = { + "preview_scope": f"child:preview:{ADMIN_DOMAIN_NAME}:{self.name}", + "execute_scope": f"child:call:{ADMIN_DOMAIN_NAME}:{self.name}", + "preview_handler_name": f"admin:preview:{self.name}", + "execute_handler_name": f"admin:execute:{self.name}", + } + for field_name, expected in expected_values.items(): + if getattr(self, field_name) != expected: + raise ValueError(f"{field_name} must be {expected!r}") + if self.annotations != ADMIN_ACTION_ANNOTATIONS: + raise ValueError( + "administrative actions must be destructive, closed-world, " + "non-read-only, and confirmation-required" + ) + + rollback_values = ( + self.rollback_scope, + self.rollback_handler_name, + ) + if self.rollback_supported: + expected_rollback = ( + f"child:rollback:{ADMIN_DOMAIN_NAME}:{self.name}", + f"admin:rollback:{self.name}", + ) + if rollback_values != expected_rollback: + raise ValueError( + "rollback-enabled actions require canonical rollback " + "scope and handler bindings" + ) + elif any(value is not None for value in rollback_values): + raise ValueError( + "rollback scope and handler require rollback_supported=true" + ) + return self + + +class AdminDomainReservation(ContractModel): + """Versioned reservation that cannot expose actions in the 1.0 release.""" + + name: Identifier = ADMIN_DOMAIN_NAME + title: NonEmptyText = "Doris Administration" + description: NonEmptyText = ( + "Reserved boundary for separately reviewed Doris-changing actions." + ) + status: Literal["reserved"] = "reserved" + enabled: Literal[False] = False + discovery_scope: BindingName = ADMIN_DOMAIN_DISCOVERY_SCOPE + require_confirmation: Literal[True] = True + actions: tuple[AdminActionContract, ...] = () + + @model_validator(mode="after") + def _validate_release_reservation(self) -> Self: + if self.name != ADMIN_DOMAIN_NAME: + raise ValueError(f"administration domain name must be {ADMIN_DOMAIN_NAME!r}") + if self.discovery_scope != ADMIN_DOMAIN_DISCOVERY_SCOPE: + raise ValueError( + "administration discovery scope must use the reserved domain scope" + ) + if self.actions: + raise ValueError( + "the Doris MCP 1.0 administration reservation cannot register actions" + ) + return self + + +def administration_config_errors( + *, + enabled: Any, + require_confirmation: Any, +) -> tuple[str, ...]: + """Return fail-closed runtime errors for the 1.0 reservation.""" + errors: list[str] = [] + if not isinstance(enabled, bool): + errors.append("Administration domain enabled flag must be boolean") + elif enabled: + errors.append( + "Administration domain is reserved and cannot be enabled in 1.0" + ) + if not isinstance(require_confirmation, bool): + errors.append("Administration confirmation flag must be boolean") + elif not require_confirmation: + errors.append( + "Administration domain confirmation requirement cannot be disabled" + ) + return tuple(errors) + + +DORIS_ADMIN_DOMAIN_RESERVATION = AdminDomainReservation() + +__all__ = [ + "ADMIN_ACTION_ANNOTATIONS", + "ADMIN_DOMAIN_DISCOVERY_SCOPE", + "ADMIN_DOMAIN_NAME", + "AdminActionContract", + "AdminConfirmationContract", + "AdminDomainReservation", + "AdminRiskLevel", + "DORIS_ADMIN_DOMAIN_RESERVATION", + "administration_config_errors", +] diff --git a/doris_mcp_server/utils/config.py b/doris_mcp_server/utils/config.py index cc64943..ae33fc4 100644 --- a/doris_mcp_server/utils/config.py +++ b/doris_mcp_server/utils/config.py @@ -43,6 +43,7 @@ from ..result_limits import ( DEFAULT_RESULT_ROWS, MIN_RESULT_BYTES, ) +from ..tools.admin_domain import administration_config_errors from ..tools.tool_provider import ( ToolProviderError, normalize_tool_provider_names, @@ -856,6 +857,14 @@ class ToolExposureConfig: mode: str = "hierarchical" +@dataclass +class AdministrationConfig: + """Fail-closed reservation for future Doris-changing actions.""" + + enabled: bool = False + require_confirmation: bool = True + + @dataclass class CapabilityConfig: """Private Doris capability snapshot controls.""" @@ -944,6 +953,9 @@ class DorisConfig: tool_exposure: ToolExposureConfig = field( default_factory=ToolExposureConfig ) + administration: AdministrationConfig = field( + default_factory=AdministrationConfig + ) capability: CapabilityConfig = field( default_factory=CapabilityConfig ) @@ -1603,6 +1615,14 @@ class DorisConfig: config.tool_exposure.mode, ).strip() _mark_source(config, "mcp_tool_exposure_mode", "env") + if "MCP_ADMIN_DOMAIN_ENABLED" in os.environ: + config.administration.enabled = _str_to_bool( + os.getenv("MCP_ADMIN_DOMAIN_ENABLED") + ) + if "MCP_ADMIN_REQUIRE_CONFIRMATION" in os.environ: + config.administration.require_confirmation = _str_to_bool( + os.getenv("MCP_ADMIN_REQUIRE_CONFIRMATION") + ) if "CAPABILITY_SNAPSHOT_TTL_SECONDS" in os.environ: config.capability.snapshot_ttl_seconds = _env_int( "CAPABILITY_SNAPSHOT_TTL_SECONDS", @@ -1835,6 +1855,12 @@ class DorisConfig: "config_file", ) + if "administration" in config_data: + administration_config = config_data["administration"] + for key, value in administration_config.items(): + if hasattr(config.administration, key): + setattr(config.administration, key, value) + if "capability" in config_data: capability_config = config_data["capability"] for key, value in capability_config.items(): @@ -1881,6 +1907,12 @@ class DorisConfig: "tool_exposure": { "mode": self.tool_exposure.mode, }, + "administration": { + "enabled": self.administration.enabled, + "require_confirmation": ( + self.administration.require_confirmation + ), + }, "capability": { "snapshot_ttl_seconds": ( self.capability.snapshot_ttl_seconds @@ -2164,6 +2196,14 @@ class DorisConfig: errors.append( "MCP tool exposure mode must be hierarchical or flat" ) + errors.extend( + administration_config_errors( + enabled=self.administration.enabled, + require_confirmation=( + self.administration.require_confirmation + ), + ) + ) if not 1 <= self.capability.snapshot_ttl_seconds <= 86400: errors.append( "Capability snapshot TTL must be in the range 1-86400 seconds" @@ -2465,6 +2505,13 @@ class DorisConfig: "tool_exposure": { "mode": self.tool_exposure.mode, }, + "administration": { + "enabled": self.administration.enabled, + "require_confirmation": ( + self.administration.require_confirmation + ), + "status": "reserved", + }, "capability": { "snapshot_ttl_seconds": ( self.capability.snapshot_ttl_seconds diff --git a/test/tools/test_admin_domain.py b/test/tools/test_admin_domain.py new file mode 100644 index 0000000..54c8811 --- /dev/null +++ b/test/tools/test_admin_domain.py @@ -0,0 +1,229 @@ +# 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. + +"""Fail-closed tests for the reserved Doris administration domain.""" + +from __future__ import annotations + +from typing import Any +from unittest.mock import Mock + +import pytest +from pydantic import ValidationError + +from doris_mcp_server.main import _multiworker_environment +from doris_mcp_server.tools.admin_domain import ( + ADMIN_ACTION_ANNOTATIONS, + ADMIN_DOMAIN_DISCOVERY_SCOPE, + ADMIN_DOMAIN_NAME, + DORIS_ADMIN_DOMAIN_RESERVATION, + AdminActionContract, + AdminConfirmationContract, + AdminDomainReservation, + AdminRiskLevel, +) +from doris_mcp_server.tools.domain_catalog import DORIS_DOMAIN_CATALOG +from doris_mcp_server.tools.domain_dispatcher import ( + ToolExposureMode, + ToolNotFoundError, +) +from doris_mcp_server.tools.domain_models import ( + Availability, + AvailabilityStatus, + ChildToolDefinition, + DomainDefinition, +) +from doris_mcp_server.tools.tools_manager import DorisToolsManager +from doris_mcp_server.utils.config import DorisConfig + + +class _UnavailableProvider: + async def availability_for( + self, + domain: DomainDefinition, + child: ChildToolDefinition, + auth_context: Any | None, + ) -> Availability: + del domain, child, auth_context + return Availability( + status=AvailabilityStatus.UNKNOWN, + callable=False, + reason_code="TEST_CAPABILITY_DISABLED", + ) + + +def _manager(mode: ToolExposureMode) -> DorisToolsManager: + connection_manager = Mock() + connection_manager.config = DorisConfig() + return DorisToolsManager( + connection_manager, + domain_availability_provider=_UnavailableProvider(), + tool_exposure_mode=mode, + ) + + +def _action(**updates: Any) -> AdminActionContract: + values: dict[str, Any] = { + "name": "cancel_query", + "title": "Cancel a Doris query", + "description": "Future reviewed action contract used only for validation.", + "risk_level": AdminRiskLevel.HIGH, + "preview_scope": "child:preview:doris_admin:cancel_query", + "execute_scope": "child:call:doris_admin:cancel_query", + "preview_handler_name": "admin:preview:cancel_query", + "execute_handler_name": "admin:execute:cancel_query", + } + values.update(updates) + return AdminActionContract(**values) + + +def test_reservation_has_no_actions_and_cannot_be_enabled() -> None: + assert DORIS_ADMIN_DOMAIN_RESERVATION.to_wire() == { + "name": ADMIN_DOMAIN_NAME, + "title": "Doris Administration", + "description": ( + "Reserved boundary for separately reviewed Doris-changing actions." + ), + "status": "reserved", + "enabled": False, + "discovery_scope": ADMIN_DOMAIN_DISCOVERY_SCOPE, + "require_confirmation": True, + "actions": [], + } + + with pytest.raises(ValidationError, match="Input should be False"): + AdminDomainReservation(enabled=True) + + +def test_future_action_contract_requires_canonical_scopes_and_handlers() -> None: + action = _action() + + assert action.annotations == ADMIN_ACTION_ANNOTATIONS + assert action.confirmation == AdminConfirmationContract() + assert action.idempotency_key_field == "idempotency_key" + + with pytest.raises(ValidationError, match="preview_scope"): + _action(preview_scope="child:preview:doris_admin:other") + + with pytest.raises(ValidationError, match="execute_handler_name"): + _action(execute_handler_name="admin:execute:other") + + +def test_future_action_contract_requires_confirmation_and_rollback_pairing() -> None: + with pytest.raises(ValidationError, match="Input should be True"): + _action(confirmation={"required": False}) + + with pytest.raises(ValidationError, match="rollback scope"): + _action( + rollback_supported=True, + rollback_scope="child:rollback:doris_admin:cancel_query", + ) + + rollback = _action( + rollback_supported=True, + rollback_scope="child:rollback:doris_admin:cancel_query", + rollback_handler_name="admin:rollback:cancel_query", + ) + assert rollback.rollback_supported is True + + +def test_administration_config_is_reserved_and_fail_closed( + monkeypatch: pytest.MonkeyPatch, +) -> None: + default = DorisConfig.from_env() + assert default.administration.enabled is False + assert default.administration.require_confirmation is True + assert not [ + error + for error in default.validate() + if error.startswith("Administration") + ] + + monkeypatch.setenv("MCP_ADMIN_DOMAIN_ENABLED", "true") + enabled = DorisConfig.from_env() + assert enabled.administration.enabled is True + assert ( + "Administration domain is reserved and cannot be enabled in 1.0" + in enabled.validate() + ) + + monkeypatch.delenv("MCP_ADMIN_DOMAIN_ENABLED") + monkeypatch.setenv("MCP_ADMIN_REQUIRE_CONFIRMATION", "false") + unsafe = DorisConfig.from_env() + assert unsafe.administration.require_confirmation is False + assert ( + "Administration domain confirmation requirement cannot be disabled" + in unsafe.validate() + ) + + +def test_administration_config_is_serialized_without_enabling_actions() -> None: + config = DorisConfig() + + assert config.to_dict()["administration"] == { + "enabled": False, + "require_confirmation": True, + } + assert config.get_config_summary()["administration"] == { + "enabled": False, + "require_confirmation": True, + "status": "reserved", + } + + +def test_multiworker_environment_preserves_the_disabled_reservation() -> None: + config = DorisConfig() + + environment = _multiworker_environment( + config, + host="127.0.0.1", + port=3000, + workers=2, + ) + + assert environment["MCP_ADMIN_DOMAIN_ENABLED"] == "false" + assert environment["MCP_ADMIN_REQUIRE_CONFIRMATION"] == "true" + + [email protected] [email protected]( + "mode", + (ToolExposureMode.HIERARCHICAL, ToolExposureMode.FLAT), +) +async def test_admin_domain_is_not_listed_or_callable( + mode: ToolExposureMode, +) -> None: + manager = _manager(mode) + listed_names = {tool.name for tool in await manager.list_tools()} + + assert ADMIN_DOMAIN_NAME not in listed_names + assert not any(name.startswith(f"{ADMIN_DOMAIN_NAME}_") for name in listed_names) + assert ADMIN_DOMAIN_NAME not in { + domain.name for domain in DORIS_DOMAIN_CATALOG.domains + } + assert not any( + name.startswith(f"{ADMIN_DOMAIN_NAME}_") + for name in manager.domain_dispatcher.formal_flat_names + ) + + with pytest.raises(ToolNotFoundError, match="Tool not found"): + await manager.call_tool(ADMIN_DOMAIN_NAME, {}) + with pytest.raises(ToolNotFoundError, match="Tool not found"): + await manager.call_tool( + f"{ADMIN_DOMAIN_NAME}_cancel_query", + {}, + ) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
