This is an automated email from the ASF dual-hosted git repository. FreeOnePlus pushed a commit to branch agent/proto-018-version-identity in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
commit 544028a45565d46e0bc2357147a4be0452ddbfd3 Author: FreeOnePlus <[email protected]> AuthorDate: Wed Jul 29 18:53:06 2026 +0800 fix: unify product version identity --- .env.example | 2 +- doris_mcp_client/client.py | 7 ++++ doris_mcp_server/__init__.py | 3 +- doris_mcp_server/{__init__.py => _version.py} | 12 +----- doris_mcp_server/main.py | 20 +++++++--- doris_mcp_server/multiworker_app.py | 8 ++-- doris_mcp_server/utils/config.py | 7 ++-- pyproject.toml | 5 ++- test/integration/test_real_doris_transports.py | 7 ++++ test/protocol/conformance_server.py | 3 +- test/protocol/stdio_capability_server.py | 3 +- test/protocol/test_mcp_v2_protocol.py | 16 +++++++- test/protocol/test_multiworker_config.py | 21 ++++++++++- test/test_product_identity.py | 51 ++++++++++++++++++++++++++ test/tools/test_tools_operation_guard.py | 3 +- uv.lock | 3 +- 16 files changed, 134 insertions(+), 37 deletions(-) diff --git a/.env.example b/.env.example index b347498..bc9cd56 100644 --- a/.env.example +++ b/.env.example @@ -401,7 +401,7 @@ ALERT_WEBHOOK_URL= # Basic server information SERVER_NAME=doris-mcp-server -SERVER_VERSION=0.6.0 +# Product version is defined by the installed package and is not configurable. SERVER_PORT=3000 # Temporary files directory diff --git a/doris_mcp_client/client.py b/doris_mcp_client/client.py index 92ee351..ba87984 100644 --- a/doris_mcp_client/client.py +++ b/doris_mcp_client/client.py @@ -39,6 +39,8 @@ from mcp.types import ( Tool, ) +from doris_mcp_server import __version__ + # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -463,6 +465,11 @@ def create_arg_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Connect to an Apache Doris MCP server using SDK 2.0.", ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {__version__}", + ) parser.add_argument( "--transport", choices=["http", "stdio"], diff --git a/doris_mcp_server/__init__.py b/doris_mcp_server/__init__.py index 4913b58..711b22c 100644 --- a/doris_mcp_server/__init__.py +++ b/doris_mcp_server/__init__.py @@ -24,6 +24,7 @@ This package provides: - Enterprise-grade security and monitoring """ -__version__ = "0.6.1" +from ._version import __version__ + __author__ = "Doris MCP Team" __description__ = "Apache Doris MCP Server Implementation" diff --git a/doris_mcp_server/__init__.py b/doris_mcp_server/_version.py similarity index 66% copy from doris_mcp_server/__init__.py copy to doris_mcp_server/_version.py index 4913b58..55dd956 100644 --- a/doris_mcp_server/__init__.py +++ b/doris_mcp_server/_version.py @@ -14,16 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -""" -Doris MCP Server - A Model Context Protocol server for Apache Doris database integration. - -This package provides: -- MCP protocol implementation for Apache Doris -- Multi-transport support (stdio, SSE, streamable HTTP) -- Comprehensive database tools and resources -- Enterprise-grade security and monitoring -""" +"""Build-time product version source.""" __version__ = "0.6.1" -__author__ = "Doris MCP Team" -__description__ = "Apache Doris MCP Server Implementation" diff --git a/doris_mcp_server/main.py b/doris_mcp_server/main.py index fac7aec..ac343a8 100644 --- a/doris_mcp_server/main.py +++ b/doris_mcp_server/main.py @@ -28,6 +28,7 @@ import logging import os import sys +from ._version import __version__ from .protocol import create_doris_mcp_server, create_transport_security from .tools.prompts_manager import DorisPromptsManager from .tools.resources_manager import DorisResourcesManager @@ -65,7 +66,6 @@ def _multiworker_environment( "SERVER_HOST": host, "SERVER_PORT": str(port), "SERVER_NAME": config.server_name, - "SERVER_VERSION": config.server_version, "TRANSPORT": "http", "WORKERS": str(workers), } @@ -206,7 +206,13 @@ class DorisServer: # Health check endpoint async def health_check(request): - return JSONResponse({"status": "healthy", "service": "doris-mcp-server"}) + return JSONResponse( + { + "status": "healthy", + "service": self.config.server_name, + "version": __version__, + } + ) # OAuth endpoints from .auth.oauth_handlers import OAuthHandlers @@ -443,6 +449,12 @@ Examples: """ ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {__version__}", + ) + parser.add_argument( "--transport", type=str, @@ -530,10 +542,6 @@ def update_configuration(config: DorisConfig): server_name = os.getenv("SERVER_NAME") if server_name: config.server_name = server_name - server_version = os.getenv("SERVER_VERSION") - if server_version: - config.server_version = server_version - # database if cli_has("--doris-host", "--db-host"): config.database.host = args.doris_host diff --git a/doris_mcp_server/multiworker_app.py b/doris_mcp_server/multiworker_app.py index 8fae732..54b7418 100644 --- a/doris_mcp_server/multiworker_app.py +++ b/doris_mcp_server/multiworker_app.py @@ -25,12 +25,12 @@ robust architecture as the single-worker mode. import os from contextlib import asynccontextmanager -from importlib.metadata import version as distribution_version from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route +from ._version import __version__ from .protocol import create_doris_mcp_server, create_transport_security from .tools.prompts_manager import DorisPromptsManager from .tools.resources_manager import DorisResourcesManager @@ -45,8 +45,6 @@ from .utils.config import ( from .utils.db import DorisConnectionManager from .utils.security import DorisSecurityManager -MCP_VERSION = distribution_version("mcp") - # Global variables for worker-specific instances _worker_server = None _worker_session_manager = None @@ -158,7 +156,7 @@ async def health_check(request): "worker_pid": os.getpid(), "worker_mode": "multi-process-full-mcp", "mcp_initialized": _worker_initialized, - "mcp_version": MCP_VERSION + "version": __version__, }) # OAuth and Token handlers (initialize after worker setup) @@ -293,7 +291,7 @@ async def root_info(request): "mode": "multi-worker-full-mcp", "worker_pid": os.getpid(), "mcp_initialized": _worker_initialized, - "mcp_version": MCP_VERSION, + "version": __version__, "endpoints": { "health": "/health", "mcp": "/mcp" diff --git a/doris_mcp_server/utils/config.py b/doris_mcp_server/utils/config.py index 630f1d8..7031aca 100644 --- a/doris_mcp_server/utils/config.py +++ b/doris_mcp_server/utils/config.py @@ -29,6 +29,8 @@ from pathlib import Path from typing import Any from urllib.parse import urlparse +from .._version import __version__ + try: from dotenv import load_dotenv except ImportError: @@ -492,7 +494,7 @@ class DorisConfig: # Basic configuration server_name: str = "doris-mcp-server" - server_version: str = "0.6.1" + server_version: str = field(default=__version__, init=False) server_host: str = "localhost" server_port: int = 3000 transport: str = "stdio" @@ -918,7 +920,6 @@ class DorisConfig: # Server configuration config.server_name = os.getenv("SERVER_NAME", config.server_name) - config.server_version = os.getenv("SERVER_VERSION", config.server_version) server_host = os.getenv("SERVER_HOST", "").strip() if server_host: config.server_host = server_host @@ -941,7 +942,7 @@ class DorisConfig: config = cls() # Update basic configuration - for key in ["server_name", "server_version", "server_port", "temp_files_dir", "transport", "workers"]: + for key in ["server_name", "server_port", "temp_files_dir", "transport", "workers"]: if key in config_data: setattr(config, key, config_data[key]) _mark_source(config, key, "config_file") diff --git a/pyproject.toml b/pyproject.toml index b52fe79..b5d5650 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ build-backend = "hatchling.build" [project] name = "doris-mcp-server" -version = "0.6.1" +dynamic = ["version"] description = "Enterprise-grade Model Context Protocol (MCP) server implementation for Apache Doris" authors = [ {name = "Yijia Su", email = "[email protected]"} @@ -154,6 +154,9 @@ Changelog = "https://github.com/apache/doris-mcp-server/blob/main/CHANGELOG.md" doris-mcp-server = "doris_mcp_server.main:main_sync" doris-mcp-client = "doris_mcp_client.client:main" +[tool.hatch.version] +path = "doris_mcp_server/_version.py" + [tool.hatch.build.targets.wheel] packages = ["doris_mcp_server", "doris_mcp_client"] diff --git a/test/integration/test_real_doris_transports.py b/test/integration/test_real_doris_transports.py index b875722..28afb28 100644 --- a/test/integration/test_real_doris_transports.py +++ b/test/integration/test_real_doris_transports.py @@ -45,6 +45,8 @@ import pytest from mcp import Client, StdioServerParameters from mcp.client.stdio import stdio_client +from doris_mcp_server import __version__ + pytestmark = [ pytest.mark.integration, pytest.mark.skipif( @@ -229,6 +231,7 @@ async def _wait_for_http_server( try: response = await client.get(f"http://127.0.0.1:{port}/health") if response.status_code == 200: + assert response.json()["version"] == __version__ return except httpx.HTTPError: pass @@ -336,6 +339,10 @@ async def test_real_doris_read_write_permission_timeout_and_recovery( password=doris_sandbox.settings.password, ) async with _transport_client(transport, admin_environment) as client: + assert client.server_info is not None + assert client.server_info.name == "doris-mcp-server" + assert client.server_info.version == __version__ + read_result, read_payload = await _exec_query(client, "SELECT 42 AS answer") assert read_result.is_error is False assert read_payload["success"] is True diff --git a/test/protocol/conformance_server.py b/test/protocol/conformance_server.py index 2cc8be8..f59b0a2 100644 --- a/test/protocol/conformance_server.py +++ b/test/protocol/conformance_server.py @@ -34,6 +34,7 @@ from mcp.types import ( Tool, ) +from doris_mcp_server import __version__ from doris_mcp_server.protocol import ( create_doris_mcp_server, create_transport_security, @@ -77,7 +78,7 @@ def create_conformance_server(): tools_manager=CapabilityToolsManager(), prompts_manager=EmptyPromptsManager(), name="doris-mcp-conformance-test", - version="0.6.1", + version=__version__, logger=logging.getLogger(__name__), required_tool_capabilities={ "test_missing_capability": ClientCapabilities(sampling=SamplingCapability()) diff --git a/test/protocol/stdio_capability_server.py b/test/protocol/stdio_capability_server.py index 25847a7..d0d222e 100644 --- a/test/protocol/stdio_capability_server.py +++ b/test/protocol/stdio_capability_server.py @@ -33,6 +33,7 @@ from mcp.types import ( Tool, ) +from doris_mcp_server import __version__ from doris_mcp_server.protocol import create_doris_mcp_server from doris_mcp_server.tools.tools_manager import DorisToolsManager from doris_mcp_server.utils.analysis_tools import SQLAnalyzer @@ -306,7 +307,7 @@ async def main() -> None: tools_manager=OneToolManager(), prompts_manager=EmptyPromptsManager(), name="doris-mcp-stdio-capability-test", - version="0.6.1", + version=__version__, logger=logging.getLogger(__name__), required_client_capabilities={ "tools/list": ClientCapabilities( diff --git a/test/protocol/test_mcp_v2_protocol.py b/test/protocol/test_mcp_v2_protocol.py index 6ee460b..a050239 100644 --- a/test/protocol/test_mcp_v2_protocol.py +++ b/test/protocol/test_mcp_v2_protocol.py @@ -36,6 +36,7 @@ from mcp.types import ( Tool, ) +from doris_mcp_server import __version__ from doris_mcp_server.protocol import ( create_doris_mcp_server, create_transport_security, @@ -155,7 +156,7 @@ def create_test_server( tools_manager=tools_manager or StubToolsManager(), prompts_manager=StubPromptsManager(), name="doris-mcp-server", - version="0.6.1", + version=__version__, logger=logging.getLogger(__name__), required_client_capabilities=required_client_capabilities, required_tool_capabilities=required_tool_capabilities, @@ -170,7 +171,7 @@ async def test_modern_and_legacy_clients_share_the_v2_protocol_core(): assert modern.protocol_version == "2026-07-28" assert modern.server_info is not None assert modern.server_info.name == "doris-mcp-server" - assert modern.server_info.version == "0.6.1" + assert modern.server_info.version == __version__ assert modern.session.discover_result is not None assert modern.session.discover_result.result_type == "complete" @@ -218,6 +219,9 @@ async def test_modern_and_legacy_clients_share_the_v2_protocol_core(): async with Client(server, mode="legacy") as legacy: assert legacy.protocol_version == "2025-11-25" + assert legacy.server_info is not None + assert legacy.server_info.name == "doris-mcp-server" + assert legacy.server_info.version == __version__ assert [tool.name for tool in (await legacy.list_tools()).tools] == [ "echo", "fail", @@ -333,6 +337,11 @@ async def test_http_discover_is_stateless_and_unknown_method_does_not_kill_serve assert first.status_code == 200 assert "mcp-session-id" not in first.headers assert first.json()["result"]["supportedVersions"] == ["2026-07-28"] + discover_server_info = first.json()["result"]["_meta"][ + "io.modelcontextprotocol/serverInfo" + ] + assert discover_server_info["name"] == "doris-mcp-server" + assert discover_server_info["version"] == __version__ unknown = await client.post( "/mcp", @@ -440,6 +449,9 @@ async def test_http_rejects_untrusted_origin_and_legacy_is_stateless(): assert initialized.status_code == 200 assert "mcp-session-id" not in initialized.headers assert initialized.json()["result"]["protocolVersion"] == "2025-11-25" + legacy_server_info = initialized.json()["result"]["serverInfo"] + assert legacy_server_info["name"] == "doris-mcp-server" + assert legacy_server_info["version"] == __version__ @pytest.mark.asyncio diff --git a/test/protocol/test_multiworker_config.py b/test/protocol/test_multiworker_config.py index 775b539..fdc5810 100644 --- a/test/protocol/test_multiworker_config.py +++ b/test/protocol/test_multiworker_config.py @@ -1,4 +1,10 @@ +import json + +import pytest + +from doris_mcp_server import __version__ from doris_mcp_server.main import _multiworker_environment +from doris_mcp_server.multiworker_app import health_check, root_info from doris_mcp_server.utils.config import DorisConfig @@ -10,7 +16,6 @@ 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.server_version = "0.6.1" worker_env = _multiworker_environment( config, @@ -18,6 +23,8 @@ def test_multiworker_environment_preserves_resolved_parent_config(monkeypatch): port=31133, workers=2, ) + assert "SERVER_VERSION" not in worker_env + monkeypatch.setenv("SERVER_VERSION", "9.9.9") for key, value in worker_env.items(): monkeypatch.setenv(key, value) @@ -30,6 +37,16 @@ def test_multiworker_environment_preserves_resolved_parent_config(monkeypatch): assert child_config.server_host == "127.0.0.1" assert child_config.server_port == 31133 assert child_config.server_name == "doris-mcp-server" - assert child_config.server_version == "0.6.1" + assert child_config.server_version == __version__ assert child_config.transport == "http" assert child_config.workers == 2 + + [email protected] +async def test_multiworker_http_identity_reports_product_version(): + for handler in (health_check, root_info): + response = await handler(None) + payload = json.loads(response.body) + assert payload["service"] == "doris-mcp-server" + assert payload["version"] == __version__ + assert "mcp_version" not in payload diff --git a/test/test_product_identity.py b/test/test_product_identity.py new file mode 100644 index 0000000..2a8d8de --- /dev/null +++ b/test/test_product_identity.py @@ -0,0 +1,51 @@ +# 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. +"""Product identity must come from one build-time version source.""" + +from importlib.metadata import version as distribution_version + +import pytest + +from doris_mcp_client.client import create_arg_parser as create_client_arg_parser +from doris_mcp_server import __version__ +from doris_mcp_server.main import create_arg_parser as create_server_arg_parser +from doris_mcp_server.utils.config import DorisConfig + + +def test_package_metadata_matches_runtime_version(): + assert distribution_version("doris-mcp-server") == __version__ + + [email protected]( + "parser_factory", + [create_server_arg_parser, create_client_arg_parser], +) +def test_cli_reports_product_version(capsys, parser_factory): + with pytest.raises(SystemExit) as exit_info: + parser_factory().parse_args(["--version"]) + + assert exit_info.value.code == 0 + assert capsys.readouterr().out.strip() == f"pytest {__version__}" + + +def test_configuration_cannot_override_product_version(monkeypatch): + monkeypatch.setenv("SERVER_VERSION", "9.9.9") + + assert DorisConfig.from_env().server_version == __version__ + assert DorisConfig._from_dict({"server_version": "9.9.9"}).server_version == ( + __version__ + ) diff --git a/test/tools/test_tools_operation_guard.py b/test/tools/test_tools_operation_guard.py index 1deedb7..447d77d 100644 --- a/test/tools/test_tools_operation_guard.py +++ b/test/tools/test_tools_operation_guard.py @@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock import mcp.types as mcp_types import pytest +from doris_mcp_server import __version__ from doris_mcp_server.auth.operation_policy import ( HIGH_RISK_TOOLS, OperationAuthorizationError, @@ -730,7 +731,7 @@ def _server_with_mock_managers(): tools_manager=server.tools_manager, prompts_manager=server.prompts_manager, name="test-doris-mcp-server", - version="0.6.1", + version=__version__, logger=server.logger, ) return server diff --git a/uv.lock b/uv.lock index 3c0ae53..ffc9188 100644 --- a/uv.lock +++ b/uv.lock @@ -591,7 +591,6 @@ wheels = [ [[package]] name = "doris-mcp-server" -version = "0.6.1" source = { editable = "." } dependencies = [ { name = "adbc-driver-flightsql" }, @@ -756,7 +755,7 @@ requires-dist = [ { name = "uvloop", marker = "extra == 'performance'", specifier = ">=0.19.0" }, { name = "websockets", specifier = ">=12.0" }, ] -provides-extras = ["dev", "docs", "performance", "monitoring"] +provides-extras = ["dev", "docs", "monitoring", "performance"] [package.metadata.requires-dev] dev = [{ name = "ruff", specifier = ">=0.11.13" }] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
