From b8e8b2f227811480fba505d32ee8611e098b40f8 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Wed, 12 Aug 2026 13:45:46 +0530 Subject: [PATCH] fix(mcp): use semantica.__version__ as authoritative MCP version source The previous implementation used importlib.metadata.version('semantica') as the primary version source with a PackageNotFoundError fallback to semantica.__version__. This caused two of the three new regression tests to fail in editable/development installs, where dist-info (egg-info) is written at install time and is not automatically updated on subsequent version bumps. In this repo, pyproject.toml declares version as a static field (not dynamic), and semantica/__init__.py maintains __version__ in sync with it by convention. semantica.__version__ is therefore the authoritative source of truth and is always present whenever semantica.mcp_server is importable -- the importlib .metadata indirection adds no value and can return a stale value. Changes: - semantica/mcp_server/__init__.py: replace the importlib.metadata try/except block with a direct 'from semantica import __version__ as _SEMANTICA_VERSION' - tests/test_mcp_server_version.py: rewrite tests to assert both MCP version surfaces (SERVER_INFO['version'] and semantica://schema/info) against semantica.__version__ as the single ground truth; add 0.4.0 regression canaries and a cross-surface consistency assertion; remove the mirrored importlib.metadata resolution that masked the staleness problem The root-level mcp/ directory (a separate unpublished companion implementation not included in the built package) is intentionally left unchanged -- it is outside the scope of issue #863 which targets the semantica-mcp entry point. --- semantica/mcp_server/__init__.py | 14 +++--- tests/test_mcp_server_version.py | 86 ++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/semantica/mcp_server/__init__.py b/semantica/mcp_server/__init__.py index cb22b434..19fe0bf4 100644 --- a/semantica/mcp_server/__init__.py +++ b/semantica/mcp_server/__init__.py @@ -45,14 +45,16 @@ import json import logging import os import sys -from importlib.metadata import PackageNotFoundError, version from typing import Any -try: - _SEMANTICA_VERSION = version("semantica") -except PackageNotFoundError: - # Preserve direct source-tree execution when distribution metadata is absent. - from semantica import __version__ as _SEMANTICA_VERSION +# `semantica.__version__` is the authoritative package version — it is kept in +# sync with pyproject.toml's static `version` field by the release process and +# is always present whenever this submodule is importable. Using it directly +# is simpler and more reliable than `importlib.metadata.version("semantica")`, +# which reads dist-info written at install time and can lag the source in +# editable installs (egg-info / dist-info is not regenerated on every version +# bump, so it can reflect a stale value). +from semantica import __version__ as _SEMANTICA_VERSION # ── logging ──────────────────────────────────────────────────────────────── _log_level = getattr(logging, os.environ.get("SEMANTICA_LOG_LEVEL", "WARNING").upper(), logging.WARNING) diff --git a/tests/test_mcp_server_version.py b/tests/test_mcp_server_version.py index 9dd9f9a4..fbe03d1d 100644 --- a/tests/test_mcp_server_version.py +++ b/tests/test_mcp_server_version.py @@ -1,35 +1,93 @@ -"""Regression tests for MCP server version reporting.""" +"""Regression tests for MCP server version reporting (issue #863). + +Both public MCP version surfaces must derive from the same authoritative +package version rather than a hardcoded stale literal: + 1. MCP ``initialize`` → ``serverInfo.version`` + 2. ``semantica://schema/info`` → ``version`` + +The authoritative source of truth is ``semantica.__version__``, which is +maintained in sync with ``pyproject.toml``'s static ``version`` field by +the release process. We assert equality against that value rather than +duplicating the version-resolution logic here, so the tests remain valid +through future version bumps without modification. + +The ``assertNotEqual(..., "0.4.0")`` canaries guard against regression to +the original stale literal that triggered issue #863. +""" import unittest -from importlib.metadata import PackageNotFoundError, version import semantica from semantica import mcp_server +_EXPECTED = semantica.__version__ + class TestMCPServerVersion(unittest.TestCase): - def test_server_info_uses_distribution_version(self): - try: - expected = version("semantica") - except PackageNotFoundError: - expected = semantica.__version__ - self.assertEqual(mcp_server.SERVER_INFO["version"], expected) + # ------------------------------------------------------------------ # + # SERVER_INFO (used directly in the initialize response) + # ------------------------------------------------------------------ # - def test_initialize_reports_package_version(self): + def test_server_info_version_matches_package(self): + """SERVER_INFO['version'] must equal the authoritative package version.""" + self.assertEqual(mcp_server.SERVER_INFO["version"], _EXPECTED) + + def test_server_info_version_is_not_stale_literal(self): + """Guard: SERVER_INFO must not report the original hardcoded 0.4.0.""" + self.assertNotEqual(mcp_server.SERVER_INFO["version"], "0.4.0") + + # ------------------------------------------------------------------ # + # MCP initialize → serverInfo.version + # ------------------------------------------------------------------ # + + def test_initialize_server_info_version_matches_package(self): + """The MCP initialize response must report the authoritative package version.""" response = mcp_server._handle( {"jsonrpc": "2.0", "id": 1, "method": "initialize"} ) - self.assertIsNotNone(response) self.assertEqual( - response["result"]["serverInfo"]["version"], semantica.__version__ + response["result"]["serverInfo"]["version"], + _EXPECTED, ) - def test_schema_info_resource_reports_package_version(self): - resource = mcp_server._read_resource("semantica://schema/info") + def test_initialize_server_info_version_is_not_stale_literal(self): + """Guard: initialize must not report the original hardcoded 0.4.0.""" + response = mcp_server._handle( + {"jsonrpc": "2.0", "id": 1, "method": "initialize"} + ) + self.assertNotEqual(response["result"]["serverInfo"]["version"], "0.4.0") - self.assertEqual(resource["version"], semantica.__version__) + # ------------------------------------------------------------------ # + # semantica://schema/info → version + # ------------------------------------------------------------------ # + + def test_schema_info_resource_version_matches_package(self): + """The semantica://schema/info resource must report the authoritative package version.""" + resource = mcp_server._read_resource("semantica://schema/info") + self.assertEqual(resource["version"], _EXPECTED) + + def test_schema_info_resource_version_is_not_stale_literal(self): + """Guard: schema/info must not report the original hardcoded 0.4.0.""" + resource = mcp_server._read_resource("semantica://schema/info") + self.assertNotEqual(resource["version"], "0.4.0") + + # ------------------------------------------------------------------ # + # Both surfaces must agree + # ------------------------------------------------------------------ # + + def test_both_version_surfaces_are_identical(self): + """SERVER_INFO and schema/info must report the exact same version string, + confirming both surfaces derive from a single authoritative value.""" + init_response = mcp_server._handle( + {"jsonrpc": "2.0", "id": 1, "method": "initialize"} + ) + schema_resource = mcp_server._read_resource("semantica://schema/info") + self.assertEqual( + init_response["result"]["serverInfo"]["version"], + schema_resource["version"], + ) if __name__ == "__main__":