implemented steps 1-5

This commit is contained in:
John Lancaster
2026-06-20 14:08:59 -05:00
parent c273ecfc54
commit 19f3c1740a
53 changed files with 1454 additions and 762 deletions
+539 -54
View File
@@ -1,74 +1,559 @@
from pathlib import Path
from __future__ import annotations
import re
from dataclasses import dataclass
from importlib.resources import files
from importlib.resources.abc import Traversable
from pathlib import PurePosixPath
from typing import Any
import yaml
from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator
def _repo_root() -> Path:
return Path(__file__).resolve().parents[3]
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.-]+)?$"
)
def resolve_skill_document_path(
*, skill_id: str, namespace: str, metadata: dict[str, Any]
) -> Path:
"""Resolve the canonical Markdown document path for a skill."""
document_path = metadata.get("document_path")
if isinstance(document_path, str) and document_path.strip():
return _repo_root() / document_path.strip()
@dataclass(frozen=True)
class RegistryIssue:
code: str
message: str
skill_id: str | None
path: str
hint: str
candidates: list[str] = []
slug = metadata.get("slug")
if isinstance(slug, str) and slug.strip():
candidates.append(slug.strip())
candidates.extend(
[
skill_id,
namespace.replace("_", "-"),
namespace,
]
class DocsRegistryValidationError(Exception):
def __init__(self, errors: list[RegistryIssue]) -> None:
self.errors = errors
summary = "\n".join(
[
(
f"{issue.code}: {issue.message} "
f"(skill={issue.skill_id or 'unknown'}, path={issue.path})"
)
for issue in errors
]
)
super().__init__(summary)
class ReferenceEntry(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
path: str
mime_type: str = "text/markdown"
title: str | None = None
@field_validator("path")
@classmethod
def validate_reference_path(cls, value: str) -> str:
path = PurePosixPath(value)
if path.is_absolute() or ".." in path.parts:
raise ValueError("reference path must be a relative in-skill path")
if not str(path).startswith("references/"):
raise ValueError("reference path must stay under references/")
if path.suffix.lower() != ".md":
raise ValueError("reference path must target a markdown file")
return path.as_posix()
class PersonalMcpMetadata(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
id: str
version: str
tags: list[str] = Field(default_factory=list)
capabilities: list[str] = Field(min_length=1)
depends_on: list[str] = Field(default_factory=list)
references: dict[str, ReferenceEntry] = Field(default_factory=dict)
@field_validator("id")
@classmethod
def validate_id(cls, value: str) -> str:
if not SKILL_ID_RE.fullmatch(value):
raise ValueError("id must be lowercase kebab-case and start with a letter")
return value
@field_validator("version")
@classmethod
def validate_version(cls, value: str) -> str:
if not SEMVER_RE.fullmatch(value):
raise ValueError("version must be semver")
return value
@field_validator("tags")
@classmethod
def validate_tags(cls, value: list[str]) -> list[str]:
for tag in value:
if not SKILL_ID_RE.fullmatch(tag):
raise ValueError(f"invalid tag: {tag}")
return value
@field_validator("depends_on")
@classmethod
def validate_depends_on(cls, value: list[str]) -> list[str]:
for dep in value:
if not SKILL_ID_RE.fullmatch(dep):
raise ValueError(f"invalid depends_on skill id: {dep}")
return value
@field_validator("references")
@classmethod
def validate_reference_ids(
cls, value: dict[str, ReferenceEntry]
) -> dict[str, ReferenceEntry]:
for ref_id in value:
if not SKILL_ID_RE.fullmatch(ref_id):
raise ValueError(f"invalid reference id: {ref_id}")
return value
class SkillFrontmatter(BaseModel):
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
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 | list[str] | None = Field(default=None, alias="allowed-tools")
disallowed_tools: str | list[str] | None = Field(
default=None,
alias="disallowed-tools",
)
disable_model_invocation: bool | None = Field(
default=None,
alias="disable-model-invocation",
)
user_invocable: bool | None = Field(default=None, alias="user-invocable")
argument_hint: str | None = Field(default=None, alias="argument-hint")
arguments: str | list[str] | None = None
license: str | None = None
compatibility: str | None = None
metadata: dict[str, str] | None = None
x_personal_mcp: PersonalMcpMetadata = Field(alias="x-personal-mcp")
seen: set[str] = set()
for candidate in candidates:
if candidate in seen:
@field_validator("name")
@classmethod
def validate_name(cls, value: str) -> str:
if not SKILL_ID_RE.fullmatch(value):
raise ValueError("name must be lowercase kebab-case and start with a letter")
if "anthropic" in value or "claude" in value:
raise ValueError("name must not contain reserved words anthropic or claude")
return value
@dataclass(frozen=True)
class ReferenceRecord:
ref_id: str
uri: str
relpath: str
mime_type: str
title: str | None
content: str
@dataclass(frozen=True)
class SkillRecord:
skill_id: str
name: str
description: str
version: str
tags: tuple[str, ...]
capabilities: tuple[str, ...]
depends_on: tuple[str, ...]
document_uri: str
document_relpath: str
document_content: str
references: dict[str, ReferenceRecord]
@dataclass(frozen=True)
class SkillSummaryRecord:
skill_id: str
name: str
description: str
tags: tuple[str, ...]
capabilities: tuple[str, ...]
document_uri: str
version: str
@dataclass(frozen=True)
class DocsRegistry:
skills_by_id: dict[str, SkillRecord]
skills_in_load_order: tuple[str, ...]
skills_summary_in_load_order: tuple[SkillSummaryRecord, ...]
docs_markdown_by_path: dict[str, str]
docs_markdown_path_index: tuple[str, ...]
tag_to_skill_ids: dict[str, tuple[str, ...]]
capability_to_skill_ids: dict[str, tuple[str, ...]
]
def _parse_frontmatter(markdown: str, *, path: str) -> tuple[dict[str, Any], str]:
if not markdown.startswith("---"):
raise ValueError(f"missing YAML frontmatter: {path}")
lines = markdown.splitlines()
if len(lines) < 3 or lines[0].strip() != "---":
raise ValueError(f"invalid YAML frontmatter start: {path}")
end_index: int | None = None
for i in range(1, len(lines)):
if lines[i].strip() == "---":
end_index = i
break
if end_index is None:
raise ValueError(f"missing YAML frontmatter terminator: {path}")
raw_yaml = "\n".join(lines[1:end_index])
body = "\n".join(lines[end_index + 1 :])
parsed = yaml.safe_load(raw_yaml)
if not isinstance(parsed, dict):
raise ValueError(f"frontmatter must parse to an object: {path}")
return parsed, body
def _walk_markdown(
node: Traversable,
*,
prefix: PurePosixPath = PurePosixPath(""),
) -> list[tuple[str, Traversable]]:
results: list[tuple[str, Traversable]] = []
for child in sorted(node.iterdir(), key=lambda item: item.name):
relpath = prefix.joinpath(child.name)
if child.is_dir():
results.extend(_walk_markdown(child, prefix=relpath))
continue
seen.add(candidate)
candidate_path = _repo_root() / "docs" / "skills" / candidate / "SKILL.md"
if candidate_path.exists():
return candidate_path
return _repo_root() / "docs" / "skills" / skill_id / "SKILL.md"
if not child.is_file() or not child.name.lower().endswith(".md"):
continue
results.append((relpath.as_posix(), child))
return results
def load_markdown_document(*, skill_id: str, document_path: Path) -> dict[str, str]:
"""Load an arbitrary Markdown document and expose it as a skill resource."""
if not document_path.exists():
raise FileNotFoundError(
f"Missing skill document for '{skill_id}': {document_path}"
def _validate_skill_frontmatter(raw: dict[str, Any], *, skill_dir_name: str) -> SkillFrontmatter:
model = SkillFrontmatter.model_validate(raw)
if model.name != skill_dir_name:
raise ValueError("frontmatter name must exactly match skill directory name")
if model.x_personal_mcp.id != model.name:
raise ValueError("x-personal-mcp.id must exactly match name")
expected_capability = f"resource://skills/{model.name}/document"
if expected_capability not in model.x_personal_mcp.capabilities:
raise ValueError(f"capabilities must include {expected_capability}")
return model
def _normalize_docs_path(path: str) -> str:
normalized = PurePosixPath(path)
if normalized.is_absolute() or ".." in normalized.parts:
raise ValueError("path must be a normalized docs-relative path")
if normalized.suffix.lower() != ".md":
raise ValueError("path must point to a markdown file")
return normalized.as_posix()
def _ensure_no_cycles(skills_by_id: dict[str, SkillRecord]) -> list[tuple[str, str]]:
visiting: set[str] = set()
visited: set[str] = set()
cycles: list[tuple[str, str]] = []
def walk(skill_id: str, stack: list[str]) -> None:
if skill_id in visited:
return
if skill_id in visiting:
cycle_from = stack[stack.index(skill_id) :]
cycles.append((skill_id, " -> ".join(cycle_from + [skill_id])))
return
visiting.add(skill_id)
stack.append(skill_id)
for dep in skills_by_id[skill_id].depends_on:
if dep in skills_by_id:
walk(dep, stack)
stack.pop()
visiting.remove(skill_id)
visited.add(skill_id)
for skill_id in sorted(skills_by_id):
walk(skill_id, [])
return cycles
def load_docs_registry(
*,
package_anchor: str,
docs_root: str = "docs",
) -> DocsRegistry:
docs_dir = files(package_anchor).joinpath(docs_root)
issues: list[RegistryIssue] = []
if not docs_dir.is_dir():
raise DocsRegistryValidationError(
[
RegistryIssue(
code="missing_docs_root",
message="docs root directory does not exist",
skill_id=None,
path=docs_root,
hint="configure docs_root to a valid packaged docs path",
)
]
)
docs_markdown_by_path: dict[str, str] = {}
for relpath, doc_file in _walk_markdown(docs_dir):
docs_markdown_by_path[relpath] = doc_file.read_text(encoding="utf-8")
skills_root = docs_dir.joinpath("skills")
if not skills_root.is_dir():
raise DocsRegistryValidationError(
[
RegistryIssue(
code="missing_skills_root",
message="skills directory does not exist under docs root",
skill_id=None,
path=f"{docs_root}/skills",
hint="ensure docs/skills is included in packaged docs",
)
]
)
skills_by_id: dict[str, SkillRecord] = {}
summaries: list[SkillSummaryRecord] = []
for skill_dir in sorted(skills_root.iterdir(), key=lambda item: item.name):
if not skill_dir.is_dir():
continue
skill_dir_name = skill_dir.name
skill_rel_root = PurePosixPath("skills").joinpath(skill_dir_name)
skill_doc_relpath = skill_rel_root.joinpath("SKILL.md").as_posix()
skill_doc_file = skill_dir.joinpath("SKILL.md")
if not skill_doc_file.is_file():
issues.append(
RegistryIssue(
code="missing_skill_document",
message="missing required SKILL.md",
skill_id=skill_dir_name,
path=skill_doc_relpath,
hint="add docs/skills/<skill-id>/SKILL.md",
)
)
continue
skill_markdown = skill_doc_file.read_text(encoding="utf-8")
try:
raw_frontmatter, _ = _parse_frontmatter(
skill_markdown,
path=skill_doc_relpath,
)
frontmatter = _validate_skill_frontmatter(
raw_frontmatter,
skill_dir_name=skill_dir_name,
)
except (ValueError, ValidationError) as exc:
issues.append(
RegistryIssue(
code="invalid_frontmatter",
message=str(exc),
skill_id=skill_dir_name,
path=skill_doc_relpath,
hint="fix SKILL.md YAML frontmatter to match the contract",
)
)
continue
references: dict[str, ReferenceRecord] = {}
for ref_id, ref_entry in frontmatter.x_personal_mcp.references.items():
ref_relpath = skill_rel_root.joinpath(ref_entry.path).as_posix()
if ref_relpath not in docs_markdown_by_path:
issues.append(
RegistryIssue(
code="missing_reference",
message=f"reference target is missing for ref_id '{ref_id}'",
skill_id=frontmatter.name,
path=ref_relpath,
hint="fix x-personal-mcp.references path or add the referenced markdown file",
)
)
continue
references[ref_id] = ReferenceRecord(
ref_id=ref_id,
uri=f"resource://skills/{frontmatter.name}/references/{ref_id}",
relpath=ref_relpath,
mime_type=ref_entry.mime_type,
title=ref_entry.title,
content=docs_markdown_by_path[ref_relpath],
)
skill_id = frontmatter.name
if skill_id in skills_by_id:
issues.append(
RegistryIssue(
code="duplicate_skill_id",
message="duplicate skill id discovered",
skill_id=skill_id,
path=skill_doc_relpath,
hint="ensure each skill directory has a unique id",
)
)
continue
record = SkillRecord(
skill_id=skill_id,
name=frontmatter.name,
description=frontmatter.description,
version=frontmatter.x_personal_mcp.version,
tags=tuple(frontmatter.x_personal_mcp.tags),
capabilities=tuple(frontmatter.x_personal_mcp.capabilities),
depends_on=tuple(frontmatter.x_personal_mcp.depends_on),
document_uri=f"resource://skills/{skill_id}/document",
document_relpath=skill_doc_relpath,
document_content=skill_markdown,
references=references,
)
skills_by_id[skill_id] = record
summaries.append(
SkillSummaryRecord(
skill_id=record.skill_id,
name=record.name,
description=record.description,
tags=record.tags,
capabilities=record.capabilities,
document_uri=record.document_uri,
version=record.version,
)
)
for skill_id, record in sorted(skills_by_id.items()):
for dependency in record.depends_on:
if dependency == skill_id:
issues.append(
RegistryIssue(
code="self_dependency",
message="skill must not depend on itself",
skill_id=skill_id,
path=record.document_relpath,
hint="remove the skill id from depends_on",
)
)
elif dependency not in skills_by_id:
issues.append(
RegistryIssue(
code="missing_dependency",
message=f"depends_on target '{dependency}' does not exist",
skill_id=skill_id,
path=record.document_relpath,
hint="add the missing skill or remove it from depends_on",
)
)
for cycle_start, cycle in _ensure_no_cycles(skills_by_id):
issues.append(
RegistryIssue(
code="dependency_cycle",
message=f"depends_on cycle detected: {cycle}",
skill_id=cycle_start,
path=skills_by_id[cycle_start].document_relpath,
hint="remove at least one dependency edge in the cycle",
)
)
seen_uris: set[str] = set()
for skill_id, record in sorted(skills_by_id.items()):
uris = [record.document_uri] + [ref.uri for ref in record.references.values()]
for uri in uris:
if uri in seen_uris:
issues.append(
RegistryIssue(
code="duplicate_uri",
message=f"duplicate resource URI generated: {uri}",
skill_id=skill_id,
path=record.document_relpath,
hint="ensure unique skill ids and reference ids",
)
)
seen_uris.add(uri)
if issues:
raise DocsRegistryValidationError(issues)
skill_ids = tuple(sorted(skills_by_id))
summary_by_id = {summary.skill_id: summary for summary in summaries}
ordered_summaries = tuple(summary_by_id[skill_id] for skill_id in skill_ids)
tag_index: dict[str, list[str]] = {}
capability_index: dict[str, list[str]] = {}
for skill_id in skill_ids:
record = skills_by_id[skill_id]
for tag in record.tags:
tag_index.setdefault(tag, []).append(skill_id)
for capability in record.capabilities:
capability_index.setdefault(capability, []).append(skill_id)
return DocsRegistry(
skills_by_id=skills_by_id,
skills_in_load_order=skill_ids,
skills_summary_in_load_order=ordered_summaries,
docs_markdown_by_path=docs_markdown_by_path,
docs_markdown_path_index=tuple(sorted(docs_markdown_by_path)),
tag_to_skill_ids={
key: tuple(sorted(values)) for key, values in sorted(tag_index.items())
},
capability_to_skill_ids={
key: tuple(sorted(values))
for key, values in sorted(capability_index.items())
},
)
def read_skill_document(registry: DocsRegistry, skill_id: str) -> dict[str, str]:
if skill_id not in registry.skills_by_id:
raise KeyError(f"unknown skill_id: {skill_id}")
skill = registry.skills_by_id[skill_id]
return {
"id": skill_id,
"uri": f"resource://skills/{skill_id}/document",
"id": skill.skill_id,
"uri": skill.document_uri,
"format": "markdown",
"source_path": str(document_path),
"content": document_path.read_text(encoding="utf-8"),
"source_path": f"docs/{skill.document_relpath}",
"content": skill.document_content,
}
def load_skill_document(*, skill_id: str, skill_slug: str) -> dict[str, str]:
"""Load the canonical skill markdown document for an MCP skill."""
document_path = _repo_root() / "docs" / "skills" / skill_slug / "SKILL.md"
return load_markdown_document(skill_id=skill_id, document_path=document_path)
def load_skill_document_from_metadata(
*, skill_id: str, namespace: str, metadata: dict[str, Any]
def read_skill_reference(
registry: DocsRegistry,
*,
skill_id: str,
ref_id: str,
) -> dict[str, str]:
"""Load a skill document using metadata overrides when present."""
document_path = resolve_skill_document_path(
skill_id=skill_id,
namespace=namespace,
metadata=metadata,
)
return load_markdown_document(skill_id=skill_id, document_path=document_path)
if skill_id not in registry.skills_by_id:
raise KeyError(f"unknown skill_id: {skill_id}")
skill = registry.skills_by_id[skill_id]
if ref_id not in skill.references:
raise KeyError(f"unknown ref_id '{ref_id}' for skill '{skill_id}'")
reference = skill.references[ref_id]
return {
"id": ref_id,
"skill_id": skill_id,
"uri": reference.uri,
"format": "markdown",
"source_path": f"docs/{reference.relpath}",
"content": reference.content,
}
def read_docs_markdown_path(registry: DocsRegistry, path: str) -> dict[str, str]:
normalized_path = _normalize_docs_path(path)
if normalized_path not in registry.docs_markdown_by_path:
raise KeyError(f"unknown docs path: {normalized_path}")
return {
"uri": f"resource://docs/{normalized_path}",
"format": "markdown",
"source_path": f"docs/{normalized_path}",
"content": registry.docs_markdown_by_path[normalized_path],
}