--- icon: lucide/braces --- # Frontmatter Contract This page defines the `SKILL.md` frontmatter and FastMCP metadata contract. ## Anthropic Frontmatter Support Across Anthropic API and Agent Skills surfaces: 1. Required fields for custom skill bundles are `name` and `description`. 2. `name` must be 1-64 characters, lowercase letters, numbers, and hyphens only, with no XML tags, and must not use the reserved words `anthropic` or `claude`. 3. `description` must be 1-1024 characters, non-empty, and contain no XML tags. Portable optional fields from the Agent Skills specification: 1. `license` 2. `compatibility` 3. `metadata` 4. `allowed-tools` Claude Code-specific optional fields: 1. `when_to_use` 2. `argument-hint` 3. `arguments` 4. `disable-model-invocation` 5. `user-invocable` 6. `allowed-tools` 7. `disallowed-tools` 8. `model` 9. `effort` 10. `context` 11. `agent` 12. `hooks` 13. `paths` 14. `shell` Repository contract decisions: 1. Treat `name` and `description` as required in all `SKILL.md` files. 2. Keep Anthropic-facing semantics in standard fields. 3. Keep MCP indexing metadata in a namespaced extension block. 4. Preserve forward compatibility by allowing additive optional metadata fields over time. ## Canonical Frontmatter Schema Use this two-layer pattern: 1. Anthropic layer: top-level fields intended for Anthropic and Agent Skills behavior. 2. Repository layer: one namespaced block, `x-personal-mcp`, for MCP catalog and routing metadata. Canonical shape: ```yaml --- name: description: # Optional Anthropic and Agent Skills fields when_to_use: allowed-tools: disable-model-invocation: false user-invocable: true license: compatibility: # Repository-specific metadata x-personal-mcp: id: version: tags: - capabilities: - resource://skills//document depends_on: [] references: : path: references/.md mime_type: text/markdown title: --- ``` ## Repository Metadata Field Rules Rules for `x-personal-mcp`: 1. `id` is required, must follow the skill id rules from the content contract, and must equal the directory name. 2. `version` is required and must be a semantic version string. 3. `tags` is optional and should be a list of kebab-case discovery labels. 4. `capabilities` is required and lists the MCP URIs the skill publishes. 5. `depends_on` is optional and lists other skill ids. 6. `references` is an optional map keyed by `ref-id`. Reference entry rules: 1. `ref-id` is lowercase kebab-case. 2. `path` is a skill-relative markdown path and must stay inside the same skill directory. 3. Nested folders under `references/` are allowed. 4. `mime_type` defaults to `text/markdown` when omitted. 5. `title` is an optional display label. 6. Renaming `ref-id` values is allowed when needed; optional aliases may be used during transitions. ## Validation Models The normative model uses Pydantic v2 with change-friendly validation: ```python from __future__ import annotations import re from pathlib import PurePosixPath from typing import Any from pydantic import BaseModel, ConfigDict, Field, field_validator, model_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.-]+)?$") 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: p = PurePosixPath(value) if p.is_absolute() or ".." in p.parts: raise ValueError("reference path must be a relative in-skill path") if not str(p).startswith("references/"): raise ValueError("reference path must stay under references/") if p.suffix.lower() != ".md": raise ValueError("reference path must target a markdown file") return str(p) 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("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 @model_validator(mode="after") def ensure_primary_capability(self) -> "PersonalMcpMetadata": expected = f"resource://skills/{self.id}/document" if expected not in self.capabilities: raise ValueError(f"capabilities must include {expected}") return self 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") @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 @model_validator(mode="after") def cross_validate(self) -> "SkillFrontmatter": if self.x_personal_mcp.id != self.name: raise ValueError("x-personal-mcp.id must exactly match name") return self 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") return model ``` Validation behavior contract: 1. Validate required core fields and relationships during registry load before FastMCP resource or tool registration. 2. Allow unknown additive fields so frontmatter can evolve without blocking startup. 3. Treat hard contract violations, including missing required fields, invalid ids, and broken required mappings, as startup errors. 4. Treat non-critical compatibility issues as warnings when possible. 5. Error messages should include the skill path and failing field for CI readability. Projection mode contract for Anthropic API upload pipelines: 1. Parse with `SkillFrontmatter` first. 2. Emit Anthropic-safe frontmatter with standard fields only. 3. Serialize repository metadata into standard `metadata` as namespaced keys. 4. Preserve the canonical authored source in `x-personal-mcp`; projection output is a build artifact. ## Anthropic Upload Compatibility Rule 1. Anthropic documentation guarantees behavior for standard frontmatter fields but does not explicitly guarantee handling of arbitrary unknown top-level keys. 2. Publishing pipelines that target strict API compatibility should support a projection mode that emits only standard frontmatter fields for upload. 3. In projection mode, repository extension metadata is serialized into the standard `metadata` field as namespaced keys or JSON-encoded values, while source-of-truth authoring remains in `x-personal-mcp`. ## FastMCP Native Metadata Surfaces Resources support native definition metadata: 1. `name` 2. `description` 3. `mime_type` 4. `tags` 5. `annotations`, including `readOnlyHint` and `idempotentHint` 6. `icons` 7. `meta` 8. `version` 9. `enabled`, which is deprecated in FastMCP v3 in favor of server-level enable and disable controls Resources also support runtime metadata through `ResourceContent.meta` and `ResourceResult.meta`. Tools support native definition metadata: 1. `name` 2. `description` 3. `tags` 4. `annotations`, including `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint` 5. `icons` 6. `meta` 7. `version` 8. `timeout` 9. `output_schema` 10. `run_in_thread` 11. `enabled`, which is deprecated in FastMCP v3 in favor of server-level enable and disable controls Tools also support runtime metadata through `ToolResult.meta`. ## Frontmatter To FastMCP Mapping Contract At server startup, map `x-personal-mcp` into FastMCP registration as follows: 1. `x-personal-mcp.id` defines the canonical URI namespace and identity checks. 2. `description` becomes the default description for the primary skill document resource. 3. `x-personal-mcp.tags` maps to resource and tool tags. 4. `x-personal-mcp.version` maps to resource and tool version metadata. 5. `x-personal-mcp.capabilities` becomes the registered URI list and catalog exposure. 6. `x-personal-mcp.references[*]` becomes resource templates or concrete resources with `mime_type`, read-only annotations, and `meta` that includes `skill_id`, `ref_id`, and source `path`. 7. `x-personal-mcp.depends_on` becomes catalog dependency graph metadata and validation inputs. ## Invariants This contract guarantees: 1. Anthropic-required frontmatter stays valid for custom skill upload and Claude Code loading. 2. MCP-specific metadata remains embedded in `SKILL.md` frontmatter, with no `metadata.yaml` sidecar. 3. FastMCP registration uses native metadata fields for resources and tools. 4. Reference ids and metadata can evolve with low-friction updates while internal file layout under `references/` stays refactor-friendly. ## Non-Goals This contract does not define: 1. URI versioning and deprecation rollout policy details. 2. Migration script design from existing `metadata.yaml` files. 3. Runtime caching and indexing performance tuning.