From aec3500370c9641c9b6f6fffa2bc2f7a05b82a52 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Sun, 21 Jun 2026 12:41:14 -0500 Subject: [PATCH] typing --- src/personal_mcp/registry/models/common.py | 16 +++++------- src/personal_mcp/registry/models/document.py | 3 ++- src/personal_mcp/registry/models/prompt.py | 27 ++++++++------------ src/personal_mcp/registry/models/skill.py | 8 +++--- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/personal_mcp/registry/models/common.py b/src/personal_mcp/registry/models/common.py index 4fe1da6..ec1c434 100644 --- a/src/personal_mcp/registry/models/common.py +++ b/src/personal_mcp/registry/models/common.py @@ -2,21 +2,19 @@ import re from collections.abc import Mapping from pathlib import PurePosixPath from types import MappingProxyType -from typing import TypeVar +from typing import ClassVar +from typing import Final from pydantic import BaseModel from pydantic import ConfigDict from pydantic import field_validator -SKILL_ID_RE = re.compile(r"^[a-z][a-z0-9-]*$") -SEMVER_RE = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+][0-9A-Za-z.-]+)?$") - -K = TypeVar("K") -V = TypeVar("V") +SKILL_ID_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z][a-z0-9-]*$") +SEMVER_RE: Final[re.Pattern[str]] = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+][0-9A-Za-z.-]+)?$") class StrictFrozenModel(BaseModel): - model_config = ConfigDict( + model_config: ClassVar[ConfigDict] = ConfigDict( extra="forbid", frozen=True, validate_by_alias=True, @@ -25,8 +23,8 @@ class StrictFrozenModel(BaseModel): ) -def frozen_mapping(value: Mapping[K, V] | None = None) -> Mapping[K, V]: - return MappingProxyType(dict(value or {})) +def frozen_mapping[K, V](value: Mapping[K, V] | None = None) -> Mapping[K, V]: + return MappingProxyType(dict(value) if value is not None else {}) class ReferenceEntry(StrictFrozenModel): diff --git a/src/personal_mcp/registry/models/document.py b/src/personal_mcp/registry/models/document.py index 4198a0b..ebd7234 100644 --- a/src/personal_mcp/registry/models/document.py +++ b/src/personal_mcp/registry/models/document.py @@ -1,4 +1,5 @@ from pathlib import PurePosixPath +from typing import Self from .common import StrictFrozenModel @@ -24,7 +25,7 @@ class SkillFilesBundleModel(StrictFrozenModel): skill: MarkdownDocumentModel, references: tuple[MarkdownDocumentModel, ...] = (), other: tuple[MarkdownDocumentModel, ...] = (), - ) -> "SkillFilesBundleModel": + ) -> Self: # Ensure deterministic ordering even when the source collection is unordered. references_sorted = tuple(sorted(references, key=lambda doc: doc.relpath)) other_sorted = tuple(sorted(other, key=lambda doc: doc.relpath)) diff --git a/src/personal_mcp/registry/models/prompt.py b/src/personal_mcp/registry/models/prompt.py index 5f9aaec..6376f21 100644 --- a/src/personal_mcp/registry/models/prompt.py +++ b/src/personal_mcp/registry/models/prompt.py @@ -1,6 +1,7 @@ import re from collections.abc import Mapping from typing import Any +from typing import Literal from pydantic import Field from pydantic import field_validator @@ -10,29 +11,23 @@ from .common import SKILL_ID_RE from .common import StrictFrozenModel from .common import frozen_mapping +type PromptArgumentType = Literal[ + "string", + "number", + "integer", + "boolean", + "array", + "object", +] + class PromptArgumentEntry(StrictFrozenModel): - type: str = Field(min_length=1) + type: PromptArgumentType description: str | None = None required: bool = False default: Any | None = None enum: tuple[str, ...] | None = None - @field_validator("type") - @classmethod - def validate_type(cls, value: str) -> str: - allowed_types = { - "string", - "number", - "integer", - "boolean", - "array", - "object", - } - if value not in allowed_types: - raise ValueError(f"unsupported prompt argument type: {value}") - return value - @field_validator("enum") @classmethod def validate_enum(cls, value: tuple[str, ...] | None) -> tuple[str, ...] | None: diff --git a/src/personal_mcp/registry/models/skill.py b/src/personal_mcp/registry/models/skill.py index 240ac9a..144fa66 100644 --- a/src/personal_mcp/registry/models/skill.py +++ b/src/personal_mcp/registry/models/skill.py @@ -9,6 +9,8 @@ from .common import ReferenceEntry from .common import StrictFrozenModel from .common import frozen_mapping +type ToolSelector = str | tuple[str, ...] + class SkillMetadata(StrictFrozenModel): id: str @@ -66,8 +68,8 @@ class SkillFrontmatter(StrictFrozenModel): name: str = Field(min_length=1, max_length=64) description: str = Field(min_length=1, max_length=1024) when_to_use: str | None = None - allowed_tools: str | tuple[str, ...] | None = Field(default=None, alias="allowed-tools") - disallowed_tools: str | tuple[str, ...] | None = Field( + allowed_tools: ToolSelector | None = Field(default=None, alias="allowed-tools") + disallowed_tools: ToolSelector | None = Field( default=None, alias="disallowed-tools", ) @@ -77,7 +79,7 @@ class SkillFrontmatter(StrictFrozenModel): ) user_invocable: bool | None = Field(default=None, alias="user-invocable") argument_hint: str | None = Field(default=None, alias="argument-hint") - arguments: str | tuple[str, ...] | None = None + arguments: ToolSelector | None = None license: str | None = None compatibility: str | None = None metadata: Mapping[str, str] | None = None