Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import re | |
| from dataclasses import dataclass | |
| from typing import Any, Mapping, Optional | |
| class TenantValidationError(ValueError): | |
| """Raised when tenant metadata is missing or malformed.""" | |
| TENANT_ID_PATTERN = re.compile(r"^[A-Za-z0-9_\-.:/]{3,128}$") | |
| class TenantContext: | |
| tenant_id: str | |
| user_id: Optional[str] = None | |
| metadata: Optional[dict[str, Any]] = None | |
| def _extract_tenant_id(payload: Mapping[str, Any]) -> str: | |
| for key in ("tenant_id", "tenantId", "tenant"): | |
| if key in payload: | |
| value = payload[key] | |
| if isinstance(value, str): | |
| return value.strip() | |
| raise TenantValidationError("tenant_id is required for every MCP tool call") | |
| def _normalize_tenant_id(raw_value: str) -> str: | |
| normalized = raw_value.strip() | |
| if not normalized: | |
| raise TenantValidationError("tenant_id cannot be empty") | |
| if not TENANT_ID_PATTERN.match(normalized): | |
| raise TenantValidationError( | |
| "tenant_id must be 3-128 chars and may only contain letters, numbers, '.', '-', '_', or ':'" | |
| ) | |
| return normalized | |
| def build_tenant_context(payload: Mapping[str, Any]) -> TenantContext: | |
| tenant_id = _normalize_tenant_id(_extract_tenant_id(payload)) | |
| user_id: Optional[str] = None | |
| metadata: Optional[dict[str, Any]] = None | |
| for key in ("user_id", "userId"): | |
| if key in payload and isinstance(payload[key], str): | |
| user_id = payload[key].strip() or None | |
| break | |
| meta_candidate = payload.get("metadata") | |
| if isinstance(meta_candidate, dict): | |
| metadata = meta_candidate | |
| return TenantContext(tenant_id=tenant_id, user_id=user_id, metadata=metadata) | |