#25911·mlflow

[BUG] MCP server versions differing only in case collide on MySQL and SQL Server

Author: robinnarsinghranabhatCreated Sep 16, 2026Updated Sep 17, 2026
Labelsbugarea/trackinghas-closing-prready

[!WARNING] Before submitting a PR, please make sure that:

  • A maintainer has triaged this issue and applied the ready label
  • This issue has no assignee
  • No duplicate PR exists

PRs not meeting these requirements may be automatically closed.

Issues Policy acknowledgement

  • I have read and agree to submit bug reports in accordance with the issues policy

Where did you encounter this bug?

Local machine

MLflow version

mlflow % uv run pip list | grep -i mlflow

clint                                    0.1.0  
mlflow                                   3.15.3.dev0   

System information

  • OS Platform and Distribution (e.g., Linux Ubuntu 16.04): ProductName: macOS ProductVersion: 26.6.2 BuildVersion: 25G83
  • Python version: Python 3.10.19

Describe the problem

MySQL and SQL Server give string columns a case-insensitive collation by default (utf8mb4_0900_ai_ci and SQL_Latin1_General_CP1_CI_AS). mcp_server_versions.version inherits this.

That column holds SemVer strings, and SemVer §11 compares prerelease identifiers as ASCII: 1.0.0-A and 1.0.0-a are two different versions. Build metadata is likewise case-sensitive. Above databases treats them as the same value.

Reproduce in MYSQL

Save this as repro.py in the repository root:

python
import uuid

from mlflow.entities.mcp_server import MCPStatus
from mlflow.environment_variables import MLFLOW_TRACKING_URI
from mlflow.exceptions import MlflowException
from mlflow.store.tracking.sqlalchemy_store import SqlAlchemyStore

store = SqlAlchemyStore(MLFLOW_TRACKING_URI.get(), "file:///tmp/artifacts")
server = f"io.github.test/case-{uuid.uuid4().hex[:8]}"
store.create_mcp_server(name=server)

# Two versions of one server, differing only in the case of the prerelease identifier.
    try:
        created = store.create_mcp_server_version(
            {"name": server, "version": version, "title": "Test"}, status=MCPStatus.ACTIVE
        )
        print(f"create({version!r}) -> stored {created.version!r}")
    except MlflowException as exc:
        print(f"create({version!r}) -> {exc.error_code}")

got = store.get_mcp_server_version(name=server, version="1.0.0-a").version
print(f"get('1.0.0-a') -> {got!r}")
print("stored:", sorted(v.version for v in store.search_mcp_server_versions(name=server)))

Then, from the repository root:

bash
./tests/db/compose.sh run --rm mlflow-mysql python repro.py

On MySQL:

create('1.0.0-A') -> stored '1.0.0-A'
create('1.0.0-a') -> RESOURCE_ALREADY_EXISTS
get('1.0.0-a')    -> '1.0.0-A'          <- wrong version returned
stored: ['1.0.0-A']                     <- one version, two were registered

SQLite and PostgreSQL compare case-sensitively and thus behave correctly

Suggested fix

Pin a case-sensitive collation on the version columns, as the skill registry tables do in :

  • mlflow/mlflow/store/db_migrations/versions/
  • mlflow/mlflow/store/tracking/dbmodels/models.py
python
# example
AGENT_PLUGIN_VERSION_STRING = (
    String(128)
    .with_variant(MYSQL_VARCHAR(128, collation="utf8mb4_bin"), "mysql")
    .with_variant(MSSQL_VARCHAR(128, collation="SQL_Latin1_General_CP1_CS_AS"), "mssql")
)

Every column holding an MCP version string must use the same type. MySQL rejects a foreign key whose columns disagree on collation (error 3780), so changing only mcp_server_versions column and not the others breaks the schema. The four columns are:

Table Column
mcp_server_versions version
mcp_server_version_tags version (FK → mcp_server_versions)
mcp_server_aliases version
mcp_access_endpoints server_version

NOTE :

  • mcp_server_versions.version_prerelease_sort_key column not require this as the encoding is digits only.
  • mcp_server_versions has shipped, so this would need an Alembic migration on a live database.

How to Test Fix

  • Refer tests/db/test_skill_registry_schema.py::test_db_backend_version_identity_is_case_sensitive. Runs across 4 db engines.

Willingness to contribute

Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.

What component(s) does this bug affect?

  • area/tracking: Tracking Service, tracking client APIs, autologging
  • area/model-registry: Model Registry service, APIs, and the fluent client calls for Model Registry
  • area/scoring: MLflow model serving, deployment tools, Spark UDFs
  • area/evaluation: MLflow model evaluation features, evaluation metrics, and evaluation workflows
  • area/prompt: MLflow prompt engineering features, prompt templates, and prompt management
  • area/tracing: MLflow Tracing features, tracing APIs, and LLM tracing functionality
  • area/gateway: MLflow AI Gateway client APIs, server, and third-party integrations
  • area/projects: MLproject format, project running backends
  • area/uiux: Front-end, user experience, plotting
  • area/docs: MLflow documentation pages