diff --git a/src/personal_mcp/registry/models/common.py b/src/personal_mcp/registry/models/common.py index ec1c434..242cf2c 100644 --- a/src/personal_mcp/registry/models/common.py +++ b/src/personal_mcp/registry/models/common.py @@ -14,6 +14,8 @@ SEMVER_RE: Final[re.Pattern[str]] = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0 class StrictFrozenModel(BaseModel): + """Immutable base model with strict field validation rules.""" + model_config: ClassVar[ConfigDict] = ConfigDict( extra="forbid", frozen=True, @@ -28,6 +30,8 @@ def frozen_mapping[K, V](value: Mapping[K, V] | None = None) -> Mapping[K, V]: class ReferenceEntry(StrictFrozenModel): + """Reference metadata for a markdown file within a skill.""" + path: str mime_type: str = "text/markdown" title: str | None = None diff --git a/src/personal_mcp/registry/models/document.py b/src/personal_mcp/registry/models/document.py index ebd7234..cb94dc6 100644 --- a/src/personal_mcp/registry/models/document.py +++ b/src/personal_mcp/registry/models/document.py @@ -5,6 +5,8 @@ from .common import StrictFrozenModel class MarkdownDocumentModel(StrictFrozenModel): + """Normalized markdown document content with path and frontmatter.""" + relpath: str content: str frontmatter: str | None = None @@ -12,6 +14,8 @@ class MarkdownDocumentModel(StrictFrozenModel): class SkillFilesBundleModel(StrictFrozenModel): + """Grouped skill documents partitioned into skill, references, and other files.""" + slug: str skill: MarkdownDocumentModel references: tuple[MarkdownDocumentModel, ...] = () diff --git a/src/personal_mcp/registry/models/prompt.py b/src/personal_mcp/registry/models/prompt.py index 6376f21..1003ce9 100644 --- a/src/personal_mcp/registry/models/prompt.py +++ b/src/personal_mcp/registry/models/prompt.py @@ -22,6 +22,8 @@ type PromptArgumentType = Literal[ class PromptArgumentEntry(StrictFrozenModel): + """Schema for a single prompt argument definition.""" + type: PromptArgumentType description: str | None = None required: bool = False @@ -37,6 +39,8 @@ class PromptArgumentEntry(StrictFrozenModel): class PromptMetadata(StrictFrozenModel): + """Canonical metadata describing a prompt contract and arguments.""" + id: str version: str tags: tuple[str, ...] = () @@ -80,6 +84,8 @@ class PromptMetadata(StrictFrozenModel): class PromptFrontmatter(StrictFrozenModel): + """Parsed PROMPT frontmatter including personal-mcp metadata.""" + name: str = Field(min_length=1, max_length=64) description: str = Field(min_length=1, max_length=1024) x_personal_mcp: PromptMetadata = Field(alias="x-personal-mcp") @@ -95,6 +101,8 @@ class PromptFrontmatter(StrictFrozenModel): class PromptDocumentModel(StrictFrozenModel): + """Structured representation of a prompt markdown document.""" + prompt_id: str relpath: str content: str diff --git a/src/personal_mcp/registry/models/registry.py b/src/personal_mcp/registry/models/registry.py index 17d1602..83cff48 100644 --- a/src/personal_mcp/registry/models/registry.py +++ b/src/personal_mcp/registry/models/registry.py @@ -9,6 +9,8 @@ from .prompt import PromptArgumentEntry class ReferenceRecord(StrictFrozenModel): + """Registry record for a resolved skill reference document.""" + ref_id: str uri: str relpath: str @@ -18,6 +20,8 @@ class ReferenceRecord(StrictFrozenModel): class SkillRecord(StrictFrozenModel): + """Registry record containing a fully resolved skill and references.""" + skill_id: str name: str description: str @@ -37,6 +41,8 @@ class SkillRecord(StrictFrozenModel): class SkillSummaryRecord(StrictFrozenModel): + """Compact skill summary exposed by catalog listing APIs.""" + skill_id: str name: str description: str @@ -47,6 +53,8 @@ class SkillSummaryRecord(StrictFrozenModel): class PromptRecord(StrictFrozenModel): + """Registry record containing a fully resolved prompt document.""" + prompt_id: str name: str description: str @@ -65,6 +73,8 @@ class PromptRecord(StrictFrozenModel): class PromptSummaryRecord(StrictFrozenModel): + """Compact prompt summary exposed by catalog listing APIs.""" + prompt_id: str name: str description: str @@ -75,6 +85,8 @@ class PromptSummaryRecord(StrictFrozenModel): class DocsRegistry(StrictFrozenModel): + """In-memory index of loaded skills, prompts, and docs content.""" + skills_by_id: Mapping[str, SkillRecord] = Field(default_factory=frozen_mapping) skills_in_load_order: tuple[str, ...] skills_summary_in_load_order: tuple[SkillSummaryRecord, ...] diff --git a/src/personal_mcp/registry/models/skill.py b/src/personal_mcp/registry/models/skill.py index a83205b..ec143ea 100644 --- a/src/personal_mcp/registry/models/skill.py +++ b/src/personal_mcp/registry/models/skill.py @@ -1,4 +1,5 @@ from collections.abc import Mapping +from pathlib import PurePosixPath from pydantic import Field from pydantic import field_validator @@ -13,6 +14,8 @@ type ToolSelector = str | tuple[str, ...] class SkillMetadata(StrictFrozenModel): + """Canonical metadata describing a skill and its dependencies.""" + id: str version: str tags: tuple[str, ...] = () @@ -65,6 +68,8 @@ class SkillMetadata(StrictFrozenModel): class SkillFrontmatter(StrictFrozenModel): + """Parsed SKILL frontmatter including standard and personal-mcp fields.""" + name: str = Field(min_length=1, max_length=64) description: str = Field(min_length=1, max_length=1024) when_to_use: str | None = None @@ -103,14 +108,18 @@ class SkillFrontmatter(StrictFrozenModel): class SkillDocumentModel(StrictFrozenModel): + """Structured representation of a skill markdown document.""" + skill_id: str - relpath: str + relpath: PurePosixPath content: str frontmatter: SkillFrontmatter class SkillReferenceDocumentModel(StrictFrozenModel): + """Structured representation of a skill reference markdown document.""" + ref_id: str - relpath: str + relpath: PurePosixPath content: str entry: ReferenceEntry