Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8350a31
[Feature] Full OpenAPI coverage for MCP and Studios, with token permi…
wangxingjun778 Sep 1, 2026
49af744
[Docs] Add the missing 0.3.x release notes and condense the News section
wangxingjun778 Sep 1, 2026
db79f8b
[Docs] Merge like-category entries and tighten the News bullets
wangxingjun778 Sep 1, 2026
82b458c
[Docs] Consolidate News entries by category
wangxingjun778 Sep 1, 2026
f8c8c26
ok Merge branch 'main' of github.com:modelscope/modelscope_hub into f…
wangxingjun778 Sep 1, 2026
60ece18
[Feature] Add OpenAPI-first Agent-IDP support
wangxingjun778 Sep 1, 2026
e8f79d2
update readme
wangxingjun778 Sep 1, 2026
482e42d
ok Merge branch 'main' of github.com:modelscope/modelscope_hub into f…
wangxingjun778 Sep 9, 2026
cb7602a
bump version
wangxingjun778 Sep 9, 2026
f357a12
add del files in upload manager
wangxingjun778 Sep 9, 2026
685993b
ok Merge branch 'main' of github.com:modelscope/modelscope_hub into f…
wangxingjun778 Sep 9, 2026
f52fd4a
fix delete files
wangxingjun778 Sep 10, 2026
e0ff178
ok Merge branch 'main' of github.com:modelscope/modelscope_hub into f…
wangxingjun778 Sep 14, 2026
f9db988
Fix agent permissions and MCP pagination
zc277584121 Sep 15, 2026
acbe22f
ok Merge branch 'main' of github.com:modelscope/modelscope_hub into f…
wangxingjun778 Sep 15, 2026
ad02a05
Harden MCP list validation
zc277584121 Sep 15, 2026
dc7311f
fix constants issue
wangxingjun778 Sep 15, 2026
9fbdc60
refactor(constants): make root module the single source of truth for …
zc277584121 Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions src/modelscope_hub/_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,62 @@ def _flatten_mcp_list_params(body: Mapping[str, Any]) -> QueryParams:
params.append((key, str(value).lower() if isinstance(value, bool) else str(value)))
return params

@staticmethod
def _decode_mcp_list_response(response: requests.Response) -> JSON:
"""Decode MCP list replies, including legacy HTTP-200 error envelopes.

The service normally signals errors through HTTP status codes. Some
deployments instead return ``200`` with ``success: false`` and an
OpenAPI-style error code. Treating that dictionary as a list payload
silently turned invalid request bodies into empty server lists.
"""
try:
payload = response.json()
except ValueError:
return {"raw_response": response.text}
if not isinstance(payload, dict):
return payload

success = payload.get("success") if "success" in payload else payload.get("Success")
if success is False:
code = payload.get("code") if payload.get("code") is not None else payload.get("Code")
message = next(
(
value.strip()
for key in ("message", "Message", "msg", "Msg", "detail", "Detail")
if isinstance(value := payload.get(key), str) and value.strip()
),
"MCP server list request was rejected.",
)
request_id = payload.get("request_id") or payload.get("requestId") or payload.get("RequestId")
code_text = str(code).strip()
error_cls: type[APIError] = APIError
status_code = 400
if code_text == "InputParameterError":
error_cls = InvalidParameter
elif code_text == "InvalidAuthentication":
error_cls = AuthenticationError
status_code = 401
elif code_text == "OperationNotAllowed":
error_cls = PermissionDeniedError
status_code = 403
elif code_text == "RateLimitExceed":
error_cls = RateLimitError
status_code = 429
raise error_cls(
message,
status_code=status_code,
request_id=request_id,
response_body=payload,
url=response.url,
method=response.request.method if response.request else "PUT",
)
if "data" in payload:
return payload["data"]
if "Data" in payload:
return payload["Data"]
return payload

@staticmethod
def _is_method_or_route_unsupported(exc: APIError) -> bool:
return exc.status_code in (404, 405, 501)
Expand Down Expand Up @@ -1309,6 +1365,8 @@ def list_mcp_servers(
"""
if isinstance(page_number, bool) or not isinstance(page_number, int) or page_number < 1:
raise InvalidParameter("page_number must be an integer >= 1.")
if isinstance(page_size, bool) or not isinstance(page_size, int) or page_size < 1:
raise InvalidParameter("page_size must be an integer >= 1.")
if page_number * page_size > 100:
# The service enforces this itself, answering 403 QuotaLimitExceed with
# exactly this rule. Checking here spares the round trip and reports it
Expand All @@ -1328,29 +1386,33 @@ def list_mcp_servers(
body = {k: v for k, v in body.items() if v is not None}

def _get() -> JSON:
return self._request(
response = self._request(
"GET",
"/mcp/servers",
params=self._flatten_mcp_list_params(body),
require_token=False,
required_scope=TokenScope.READ,
anonymous_retry=True,
unwrap=False,
)
return self._decode_mcp_list_response(response)

if self._mcp_list_supports_get:
# This deployment already answered GET and refused PUT, so leading
# with PUT again would just repeat a known 404 on every call.
return _get()

try:
return self._request(
response = self._request(
"PUT",
"/mcp/servers",
json_body=body,
require_token=False,
required_scope=TokenScope.READ,
anonymous_retry=True,
unwrap=False,
)
return self._decode_mcp_list_response(response)
except APIError as exc:
if self._mcp_list_supports_get is False or not self._is_method_or_route_unsupported(exc):
raise
Expand Down
101 changes: 49 additions & 52 deletions src/modelscope_hub/compat/constants.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,79 @@
"""Legacy constant mappings for backward compatibility with modelscope SDK."""
"""Legacy constant mappings for backward compatibility with the modelscope SDK.

from ..constants import (
Every name here is re-exported from :mod:`modelscope_hub.constants`, the single
source of truth. This module defines no independent values of its own; it only
surfaces the historical names (plus a few integer / env-name aliases) that the
modelscope SDK imports.
"""

from ..constants import ( # noqa: F401
DEFAULT_CREDENTIALS_PATH,
DEFAULT_DATASET_REVISION,
DEFAULT_MAX_WORKERS,
DEFAULT_MODELSCOPE_DATA_ENDPOINT,
DEFAULT_MODELSCOPE_DOMAIN,
DEFAULT_MODELSCOPE_GROUP,
DEFAULT_MODELSCOPE_INTL_DATA_ENDPOINT,
DEFAULT_MODELSCOPE_INTL_DOMAIN,
DEFAULT_SKILLS_DIR,
FILE_HASH,
MODEL_ID_SEPARATOR,
REPO_TYPE_DATASET,
REPO_TYPE_MODEL,
REPO_TYPE_STUDIO,
REPO_TYPE_SUPPORT,
TEMPORARY_FOLDER_NAME,
UPLOAD_ADAPTIVE_BATCH_SIZE,
UPLOAD_ADAPTIVE_BATCHING_ENABLED,
UPLOAD_BLOB_CONNECT_TIMEOUT,
UPLOAD_BLOB_CONNECT_TIMEOUT_SECONDS,
UPLOAD_BLOB_MAX_ATTEMPTS,
UPLOAD_BLOB_MAX_RETRIES,
UPLOAD_BLOB_PROGRESS_THRESHOLD_BYTES,
UPLOAD_BLOB_READ_TIMEOUT,
UPLOAD_BLOB_READ_TIMEOUT_SECONDS,
UPLOAD_BLOB_RETRY_BACKOFF,
UPLOAD_BLOB_RETRY_BACKOFF_BASE_SECONDS,
UPLOAD_BLOB_RETRY_MAX_DELAY_SECONDS,
UPLOAD_BLOB_RETRY_MAX_WAIT,
UPLOAD_BLOB_TIMEOUT,
UPLOAD_BLOB_TQDM_DISABLE_THRESHOLD,
UPLOAD_BLOB_VALIDATION_BATCH_MAX_OBJECTS,
UPLOAD_CACHE_ENABLED,
UPLOAD_COMMIT_BATCH_MAX_OPERATIONS,
UPLOAD_COMMIT_BATCH_SIZE,
UPLOAD_FAILED_FILE_MAX_RETRIES,
UPLOAD_FAILED_FILE_MAX_RETRY_ROUNDS,
UPLOAD_HTTP_RETRY_ALLOWED_METHODS,
UPLOAD_LFS_FORCE_THRESHOLD_BYTES,
UPLOAD_LFS_THRESHOLD,
UPLOAD_MAX_CONCURRENT_WORKERS,
UPLOAD_MAX_FILE_COUNT,
UPLOAD_MAX_FILE_COUNT_IN_DIR,
UPLOAD_MAX_FILE_SIZE,
UPLOAD_MAX_FILE_SIZE_BYTES,
UPLOAD_MAX_FILES_PER_DIRECTORY,
UPLOAD_NORMAL_FILE_SIZE_TOTAL_LIMIT,
UPLOAD_NORMAL_FILES_TOTAL_SIZE_BYTES,
UPLOAD_REACT_BACKOFF_MAX_EXPONENT,
UPLOAD_REACT_ENABLED,
UPLOAD_REACT_MAX_DELAY,
UPLOAD_REACT_ROUND2_BASE_DELAY,
UPLOAD_REACT_ROUND3_FILE_DELAY,
UPLOAD_RECOVERY_BACKOFF_MAX_EXPONENT,
UPLOAD_RECOVERY_ENABLED,
UPLOAD_RECOVERY_MAX_DELAY_SECONDS,
UPLOAD_RECOVERY_SERIAL_BACKOFF_BASE_SECONDS,
UPLOAD_RECOVERY_SINGLE_FILE_DELAY_SECONDS,
UPLOAD_RETRY_ALLOWED_METHODS,
UPLOAD_SIZE_THRESHOLD_TO_ENFORCE_LFS,
UPLOAD_USE_CACHE,
UPLOAD_VALIDATE_BLOB_BATCH_SIZE,
RepoType,
Visibility,
get_upload_ignore_file_pattern, # noqa: F401
)
from ..constants import (
UPLOAD_LFS_THRESHOLD as _UPLOAD_LFS_THRESHOLD,
)
from ..constants import (
UPLOAD_MAX_FILE_COUNT as _UPLOAD_MAX_FILE_COUNT,
get_upload_ignore_file_pattern,
)

REPO_TYPE_MODEL: str = RepoType.MODEL.value
REPO_TYPE_DATASET: str = RepoType.DATASET.value
REPO_TYPE_STUDIO: str = RepoType.STUDIO.value
REPO_TYPE_SUPPORT: list[str] = [REPO_TYPE_MODEL, REPO_TYPE_DATASET, REPO_TYPE_STUDIO]

DEFAULT_DATASET_REVISION: str = "master"
DEFAULT_MAX_WORKERS: int = UPLOAD_MAX_CONCURRENT_WORKERS

# Legacy upload constants. Values come from the modelscope-hub runtime config.
UPLOAD_BLOB_CONNECT_TIMEOUT: int = UPLOAD_BLOB_CONNECT_TIMEOUT_SECONDS
UPLOAD_BLOB_READ_TIMEOUT: int = UPLOAD_BLOB_READ_TIMEOUT_SECONDS
UPLOAD_BLOB_MAX_RETRIES: int = UPLOAD_BLOB_MAX_ATTEMPTS
UPLOAD_BLOB_RETRY_BACKOFF: int = UPLOAD_BLOB_RETRY_BACKOFF_BASE_SECONDS
UPLOAD_BLOB_RETRY_MAX_WAIT: int = UPLOAD_BLOB_RETRY_MAX_DELAY_SECONDS
UPLOAD_FAILED_FILE_MAX_RETRIES: int = UPLOAD_FAILED_FILE_MAX_RETRY_ROUNDS
UPLOAD_BLOB_TIMEOUT: tuple[int, int] = (
UPLOAD_BLOB_CONNECT_TIMEOUT_SECONDS,
UPLOAD_BLOB_READ_TIMEOUT_SECONDS,
)
UPLOAD_RETRY_ALLOWED_METHODS: frozenset[str] = UPLOAD_HTTP_RETRY_ALLOWED_METHODS
UPLOAD_MAX_FILE_SIZE: int = UPLOAD_MAX_FILE_SIZE_BYTES
UPLOAD_MAX_FILE_COUNT: int = _UPLOAD_MAX_FILE_COUNT
UPLOAD_SIZE_THRESHOLD_TO_ENFORCE_LFS: int = UPLOAD_LFS_FORCE_THRESHOLD_BYTES
UPLOAD_LFS_THRESHOLD: int = _UPLOAD_LFS_THRESHOLD
UPLOAD_MAX_FILE_COUNT_IN_DIR: int = UPLOAD_MAX_FILES_PER_DIRECTORY
UPLOAD_NORMAL_FILE_SIZE_TOTAL_LIMIT: int = UPLOAD_NORMAL_FILES_TOTAL_SIZE_BYTES
UPLOAD_COMMIT_BATCH_SIZE: int = UPLOAD_COMMIT_BATCH_MAX_OPERATIONS
UPLOAD_VALIDATE_BLOB_BATCH_SIZE: int = UPLOAD_BLOB_VALIDATION_BATCH_MAX_OBJECTS
UPLOAD_ADAPTIVE_BATCH_SIZE: bool = UPLOAD_ADAPTIVE_BATCHING_ENABLED
UPLOAD_REACT_ENABLED: bool = UPLOAD_RECOVERY_ENABLED
UPLOAD_REACT_ROUND2_BASE_DELAY: int = UPLOAD_RECOVERY_SERIAL_BACKOFF_BASE_SECONDS
UPLOAD_REACT_ROUND3_FILE_DELAY: int = UPLOAD_RECOVERY_SINGLE_FILE_DELAY_SECONDS
UPLOAD_REACT_BACKOFF_MAX_EXPONENT: int = UPLOAD_RECOVERY_BACKOFF_MAX_EXPONENT
UPLOAD_REACT_MAX_DELAY: int = UPLOAD_RECOVERY_MAX_DELAY_SECONDS
UPLOAD_BLOB_TQDM_DISABLE_THRESHOLD: int = UPLOAD_BLOB_PROGRESS_THRESHOLD_BYTES
UPLOAD_USE_CACHE: bool = UPLOAD_CACHE_ENABLED

# Visibility integer constants matching the old SDK
ModelVisibility_PUBLIC: int = int(Visibility.PUBLIC)
ModelVisibility_PRIVATE: int = int(Visibility.PRIVATE)
Expand All @@ -79,9 +82,3 @@
# Env var name constants (old SDK imports these as string constants)
MODELSCOPE_DOMAIN: str = "MODELSCOPE_DOMAIN"
MODELSCOPE_PREFER_AI_SITE: str = "MODELSCOPE_PREFER_AI_SITE"

# API response field names
FILE_HASH: str = "Sha256"

# Download temp folder name
TEMPORARY_FOLDER_NAME: str = "._____temp"
54 changes: 54 additions & 0 deletions src/modelscope_hub/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import warnings
from dataclasses import dataclass
from enum import Enum, IntEnum
from pathlib import Path

# ---------------------------------------------------------------------------
# StrEnum compatibility shim (Python 3.10 lacks :class:`enum.StrEnum`).
Expand Down Expand Up @@ -182,6 +183,38 @@ class License(StrEnum):
LEGACY_API_PREFIX: str = "/api/v1"


# ---------------------------------------------------------------------------
# Repo-type string aliases and shared repo defaults.
#
# The modern surface models repo kinds as :class:`RepoType`; these string
# aliases and the dataset-revision default are the historical names the
# modelscope SDK consumes. Defined here so ``modelscope_hub.constants`` is the
# single source of truth (``compat.constants`` re-exports them).
# ---------------------------------------------------------------------------
REPO_TYPE_MODEL: str = RepoType.MODEL.value
REPO_TYPE_DATASET: str = RepoType.DATASET.value
REPO_TYPE_STUDIO: str = RepoType.STUDIO.value
REPO_TYPE_SUPPORT: list[str] = [REPO_TYPE_MODEL, REPO_TYPE_DATASET, REPO_TYPE_STUDIO]
DEFAULT_DATASET_REVISION: str = "master"

# ---------------------------------------------------------------------------
# Legacy modelscope domain / endpoint / filesystem constants.
#
# Historical names and shapes (``www.`` domains, the ``damo`` group, a ``Path``
# credentials location) the modelscope SDK consumes. The modern canonical
# endpoint stays :data:`DEFAULT_ENDPOINT` (no ``www``); these coexist for
# backward compatibility and ``compat.constants`` re-exports them.
# ---------------------------------------------------------------------------
MODEL_ID_SEPARATOR: str = "/"
DEFAULT_MODELSCOPE_GROUP: str = "damo"
DEFAULT_MODELSCOPE_DOMAIN: str = "www.modelscope.cn"
DEFAULT_MODELSCOPE_INTL_DOMAIN: str = "www.modelscope.ai"
DEFAULT_MODELSCOPE_DATA_ENDPOINT: str = "https://" + DEFAULT_MODELSCOPE_DOMAIN
DEFAULT_MODELSCOPE_INTL_DATA_ENDPOINT: str = "https://" + DEFAULT_MODELSCOPE_INTL_DOMAIN
DEFAULT_SKILLS_DIR: str = os.path.join(os.path.expanduser("~"), ".agents", "skills")
DEFAULT_CREDENTIALS_PATH: Path = Path.home().joinpath(".modelscope", "credentials")


# ---------------------------------------------------------------------------
# Helpers for environment-driven overrides (auto-registering)
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -503,6 +536,9 @@ def _env_register(
FILE_HASH_FIELD: str = "Sha256"
"""API response field name for file hash."""

FILE_HASH: str = FILE_HASH_FIELD
"""Legacy alias of :data:`FILE_HASH_FIELD` (modelscope SDK name)."""

ENV_FILE_LOCK: str = "MODELSCOPE_DOWNLOAD_FILE_LOCK"
_env_register(
ENV_FILE_LOCK,
Expand Down Expand Up @@ -755,6 +791,7 @@ def get_upload_ignore_file_pattern() -> str | None:
# Deprecated Python aliases. Runtime code must use the explicit names above.
UPLOAD_BLOB_CONNECT_TIMEOUT = UPLOAD_BLOB_CONNECT_TIMEOUT_SECONDS
UPLOAD_BLOB_READ_TIMEOUT = UPLOAD_BLOB_READ_TIMEOUT_SECONDS
UPLOAD_BLOB_TIMEOUT = (UPLOAD_BLOB_CONNECT_TIMEOUT_SECONDS, UPLOAD_BLOB_READ_TIMEOUT_SECONDS)
UPLOAD_BLOB_MAX_RETRIES = UPLOAD_BLOB_MAX_ATTEMPTS
UPLOAD_BLOB_RETRY_BACKOFF = UPLOAD_BLOB_RETRY_BACKOFF_BASE_SECONDS
UPLOAD_BLOB_RETRY_MAX_WAIT = UPLOAD_BLOB_RETRY_MAX_DELAY_SECONDS
Expand All @@ -775,6 +812,7 @@ def get_upload_ignore_file_pattern() -> str | None:
DEFAULT_MAX_WORKERS = UPLOAD_MAX_CONCURRENT_WORKERS
UPLOAD_USE_CACHE = UPLOAD_CACHE_ENABLED
UPLOAD_LFS_ENFORCE_THRESHOLD = UPLOAD_LFS_FORCE_THRESHOLD_BYTES
UPLOAD_SIZE_THRESHOLD_TO_ENFORCE_LFS = UPLOAD_LFS_FORCE_THRESHOLD_BYTES
UPLOAD_MAX_FILE_COUNT_IN_DIR = UPLOAD_MAX_FILES_PER_DIRECTORY
UPLOAD_MAX_FILE_SIZE = UPLOAD_MAX_FILE_SIZE_BYTES
UPLOAD_NORMAL_FILE_SIZE_TOTAL_LIMIT = UPLOAD_NORMAL_FILES_TOTAL_SIZE_BYTES
Expand Down Expand Up @@ -923,10 +961,18 @@ def get_upload_ignore_file_pattern() -> str | None:
"CONFIG_DIR_NAME",
"DATASET_LFS_SUFFIX",
"DEFAULT_CACHE_DIR_NAME",
"DEFAULT_CREDENTIALS_PATH",
"DEFAULT_DATASET_REVISION",
"DEFAULT_ENDPOINT",
"DEFAULT_IGNORE_PATTERNS",
"DEFAULT_INTL_ENDPOINT",
"DEFAULT_MAX_WORKERS",
"DEFAULT_MODELSCOPE_DATA_ENDPOINT",
"DEFAULT_MODELSCOPE_DOMAIN",
"DEFAULT_MODELSCOPE_GROUP",
"DEFAULT_MODELSCOPE_INTL_DATA_ENDPOINT",
"DEFAULT_MODELSCOPE_INTL_DOMAIN",
"DEFAULT_SKILLS_DIR",
"DOWNLOAD_CHUNK_SIZE",
"DOWNLOAD_PARALLEL_THRESHOLD",
"DOWNLOAD_PARALLELS",
Expand All @@ -942,12 +988,18 @@ def get_upload_ignore_file_pattern() -> str | None:
"ENV_PREFER_AI_SITE",
"ENV_REGISTRY",
"EnvVar",
"FILE_HASH",
"FILE_HASH_FIELD",
"get_upload_ignore_file_pattern",
"LEGACY_API_PREFIX",
"License",
"MODEL_ID_SEPARATOR",
"MODEL_LFS_SUFFIX",
"OPENAPI_PREFIX",
"REPO_TYPE_DATASET",
"REPO_TYPE_MODEL",
"REPO_TYPE_STUDIO",
"REPO_TYPE_SUPPORT",
"RepoType",
"StrEnum",
"SESSION_FILE_NAME",
Expand All @@ -968,6 +1020,7 @@ def get_upload_ignore_file_pattern() -> str | None:
"UPLOAD_BLOB_RETRY_BACKOFF_BASE_SECONDS",
"UPLOAD_BLOB_RETRY_MAX_DELAY_SECONDS",
"UPLOAD_BLOB_RETRY_MAX_WAIT",
"UPLOAD_BLOB_TIMEOUT",
"UPLOAD_BLOB_TQDM_DISABLE_THRESHOLD",
"UPLOAD_BLOB_VALIDATION_BATCH_MAX_OBJECTS",
"UPLOAD_CACHE_ENABLED",
Expand Down Expand Up @@ -1005,6 +1058,7 @@ def get_upload_ignore_file_pattern() -> str | None:
"UPLOAD_RECOVERY_SERIAL_BACKOFF_BASE_SECONDS",
"UPLOAD_RECOVERY_SINGLE_FILE_DELAY_SECONDS",
"UPLOAD_RETRY_ALLOWED_METHODS",
"UPLOAD_SIZE_THRESHOLD_TO_ENFORCE_LFS",
"UPLOAD_USE_CACHE",
"UPLOAD_VALIDATE_BLOB_BATCH_SIZE",
"Visibility",
Expand Down
2 changes: 1 addition & 1 deletion src/modelscope_hub/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Version information for modelscope_hub."""

__version__ = "0.4.2+main"
__version__ = "0.4.3+main"
Loading
Loading