Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 660ca88e47 | |||
| e60fc4b27b | |||
| 8817d2586f | |||
| bb7508cf65 | |||
| 467e1d3c35 |
@@ -55,7 +55,7 @@ Rules:
|
||||
- No underscores, spaces, dots, or uppercase characters.
|
||||
- Directory name should equal `skill-id` in each committed revision.
|
||||
- Frontmatter `id` should equal directory name in each committed revision.
|
||||
- Prefer keeping `skill-id` stable, but renames are allowed when needed if mappings are updated together.
|
||||
- Treat `skill-id` as immutable after release; any rename is a breaking replacement and clients must move to the new id.
|
||||
|
||||
Example valid ids:
|
||||
|
||||
|
||||
@@ -72,16 +72,6 @@ Contract intent:
|
||||
- Resolves only inside `docs/`.
|
||||
- This surface is markdown-only in end state (`.md` files).
|
||||
|
||||
### Legacy URI Policy (Current-to-Target Transition)
|
||||
|
||||
Current catalog URIs in this repo (`resource://catalog/patterns`, `resource://catalog/patterns_by_id`, `resource://catalog/skills_details`) are treated as compatibility aliases during migration.
|
||||
|
||||
Rules:
|
||||
|
||||
- Keep aliases only when needed for active clients.
|
||||
- Prefer simple canonical URIs for new clients.
|
||||
- Remove aliases once consumers have moved.
|
||||
|
||||
### URI Versioning Policy
|
||||
|
||||
Default rule:
|
||||
@@ -91,30 +81,14 @@ Default rule:
|
||||
|
||||
Breaking-change rule:
|
||||
|
||||
- If clients are already using an older shape, provide either:
|
||||
- a short-lived alias, or
|
||||
- a versioned URI family such as `resource://catalog/v2/...`.
|
||||
- Choose the lightest migration path that minimizes maintenance overhead.
|
||||
- Breaking changes use direct replacement of the canonical URI family.
|
||||
- No compatibility aliases or dual URI families are maintained in this greenfield phase.
|
||||
|
||||
FastMCP version metadata usage:
|
||||
|
||||
- Resource `version` metadata MAY be used for implementation/version discovery.
|
||||
- URI readability and maintainability remain the primary contract.
|
||||
|
||||
### Deprecation Policy For URIs
|
||||
|
||||
When deprecating a URI:
|
||||
|
||||
1. Document the replacement URI in changelog/docs.
|
||||
2. Optionally return deprecation metadata while an alias exists.
|
||||
3. Remove deprecated aliases when no active client needs them.
|
||||
|
||||
Recommended deprecation metadata fields in resource responses:
|
||||
|
||||
- `deprecated: true`
|
||||
- `replacement_uri: <uri>`
|
||||
- `sunset_at: <ISO-8601 timestamp>`
|
||||
|
||||
### Reference ID Compatibility Policy
|
||||
|
||||
`ref_id` is the public identifier for a reference document, separate from file path.
|
||||
@@ -123,19 +97,14 @@ Rules:
|
||||
|
||||
- Prefer keeping `ref_id` stable when practical.
|
||||
- File paths may change without URI churn as long as the mapped `ref_id` resolves.
|
||||
- If a reference is renamed, introduce a new `ref_id`; keep an alias only if clients depend on the old id.
|
||||
- If a reference is renamed, introduce a new `ref_id` and treat the old one as retired.
|
||||
- Avoid reusing retired `ref_id` values for unrelated content.
|
||||
|
||||
Alias behavior for renamed references:
|
||||
|
||||
- If alias is kept, old `ref_id` continues to resolve and points to the replacement.
|
||||
- Remove old alias as soon as migration is complete.
|
||||
|
||||
### Invariants This Contract Guarantees
|
||||
|
||||
- One canonical URI pattern per core capability surface.
|
||||
- Fast, low-friction URI evolution with optional compatibility aliases.
|
||||
- Explicit migration path for catalog URI consolidation when needed.
|
||||
- Fast, low-friction URI evolution through direct replacement of canonical URIs.
|
||||
- A single canonical catalog URI family with no alias maintenance overhead.
|
||||
- Reference mappings can evolve with minimal churn.
|
||||
|
||||
### Non-goals For Step 3
|
||||
|
||||
@@ -50,17 +50,19 @@ Rules:
|
||||
|
||||
### Tool Fallback Surface (Normative)
|
||||
|
||||
The minimal read-only fallback tool set is:
|
||||
The fallback tool surface includes:
|
||||
|
||||
1. `search_patterns`
|
||||
2. `get_pattern_by_id`
|
||||
3. `get_skill_document_by_id`
|
||||
1. `list_resources`
|
||||
2. `read_resource`
|
||||
3. `search_patterns`
|
||||
4. `get_pattern_by_id`
|
||||
5. `get_skill_document_by_id`
|
||||
|
||||
Fallback order:
|
||||
|
||||
1. call `search_patterns` to find likely skills
|
||||
2. call `get_pattern_by_id` for one selected id when detail is needed
|
||||
3. call `get_skill_document_by_id` only for selected skill(s)
|
||||
1. call `list_resources` to inspect canonical static/template resource surfaces
|
||||
2. call `read_resource` for catalog URIs and selected skill URIs
|
||||
3. use thin catalog tools only when additional metadata-first narrowing is needed
|
||||
|
||||
Tool behavior requirements:
|
||||
|
||||
@@ -71,14 +73,14 @@ Tool behavior requirements:
|
||||
|
||||
### Resources-As-Tools Compatibility Layer
|
||||
|
||||
Step 6 may include a resources-as-tools compatibility layer for clients that can call tools but not attach resources.
|
||||
Step 6 includes a resources-as-tools compatibility layer for clients that can call tools but not attach resources.
|
||||
|
||||
Rules:
|
||||
|
||||
1. If enabled, it wraps canonical resource reads rather than re-implementing content transforms.
|
||||
1. It wraps canonical resource reads rather than re-implementing content transforms.
|
||||
2. It preserves canonical URIs and metadata semantics.
|
||||
3. It does not replace the minimal catalog tools listed above.
|
||||
4. It remains optional and interoperability-driven.
|
||||
4. It is interoperability-driven and remains read-only.
|
||||
|
||||
### Resource/Tool Parity Contract
|
||||
|
||||
@@ -144,10 +146,11 @@ Separation-of-concerns rule:
|
||||
Step 6 is complete when all are true:
|
||||
|
||||
1. Resource-first discovery remains the documented and implemented default path.
|
||||
2. The fallback tool set is minimal, read-only, and schema-aligned.
|
||||
3. Fallback tool outputs map to canonical skill identities and URIs.
|
||||
4. Context loading is bounded and clarifying-question behavior is documented for low-confidence cases.
|
||||
5. No second content source is introduced; resources and tools resolve the same authored markdown.
|
||||
2. `list_resources` and `read_resource` are available for tool-only clients.
|
||||
3. Thin catalog tools remain minimal, read-only parity surfaces.
|
||||
4. Fallback tool outputs map to canonical skill identities and URIs.
|
||||
5. Context loading is bounded and clarifying-question behavior is documented for low-confidence cases.
|
||||
6. No second content source is introduced; resources and tools resolve the same authored markdown.
|
||||
|
||||
### Non-goals for Step 6
|
||||
|
||||
|
||||
+15
-5
@@ -16,11 +16,17 @@ The system is complete in three layers:
|
||||
2. Catalog resources provide normalized discovery.
|
||||
3. Zensical builds a static site from those same Markdown sources and the FastAPI app serves it in the FastMCP runtime process.
|
||||
|
||||
For Phase 1, this architecture is anchored by three contracts:
|
||||
This architecture is anchored by three contracts:
|
||||
|
||||
1. Step 1: docs-first authored content contract under `docs/` with strict per-skill ownership.
|
||||
2. Step 2: SKILL.md frontmatter contract with Anthropic fields plus `x-personal-mcp` metadata.
|
||||
3. Step 3: canonical resource URI contract with break-and-replace policy for contract changes.
|
||||
1. Docs-first authored content contract under `docs/` with strict per-skill ownership.
|
||||
2. `SKILL.md` frontmatter contract with Anthropic fields plus `x-personal-mcp` metadata.
|
||||
3. Canonical resource URI contract with break-and-replace policy for contract changes.
|
||||
|
||||
Detailed contract pages:
|
||||
|
||||
1. [Content Contract](./content.md)
|
||||
2. [Frontmatter Contract](./frontmatter.md)
|
||||
3. [URI Contract](./uris.md)
|
||||
|
||||
This architecture keeps authored content human-friendly while preserving machine-stable contracts.
|
||||
|
||||
@@ -62,7 +68,7 @@ Only canonical catalog resources are part of the runtime contract in this phase.
|
||||
|
||||
### Registry Loader
|
||||
|
||||
Phase 2 runtime composition introduces a startup registry loader that reads packaged docs resources using `importlib.resources.files(...)` and `Traversable` APIs.
|
||||
The runtime composition includes a startup registry loader that reads packaged docs resources using `importlib.resources.files(...)` and `Traversable` APIs.
|
||||
|
||||
Loader responsibilities:
|
||||
|
||||
@@ -104,6 +110,8 @@ flowchart TD
|
||||
|
||||
Each skill declares frontmatter in `docs/skills/<skill-id>/SKILL.md`.
|
||||
|
||||
For the full field-level contract, validation model, and FastMCP metadata mapping, see [Frontmatter Contract](./frontmatter.md).
|
||||
|
||||
Anthropic-facing required fields:
|
||||
|
||||
1. name
|
||||
@@ -124,6 +132,8 @@ No `metadata.yaml` sidecar is part of the end-state contract.
|
||||
|
||||
Canonical resource URIs are:
|
||||
|
||||
For the full URI semantics, parameter validation rules, and compatibility policy, see [URI Contract](./uris.md).
|
||||
|
||||
1. resource://skills/<skill_id>/document
|
||||
2. resource://skills/<skill_id>/references/<ref_id>
|
||||
3. resource://catalog/skills_index
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
icon: lucide/file-text
|
||||
---
|
||||
|
||||
# Content Contract
|
||||
|
||||
This page defines the authored content contract for the docs-first MCP architecture.
|
||||
|
||||
## Canonical Source Of Truth
|
||||
|
||||
1. All authored Markdown lives under `docs/`.
|
||||
2. MCP resources and static docs are two distribution surfaces of the same authored files.
|
||||
3. No parallel authored markdown is allowed in `src/` or other package-only paths.
|
||||
|
||||
## Canonical Skill Shape
|
||||
|
||||
Each skill is one directory under `docs/skills/`:
|
||||
|
||||
```text
|
||||
docs/
|
||||
skills/
|
||||
<skill-id>/
|
||||
SKILL.md
|
||||
references/
|
||||
... (one or more markdown files, optional nested folders)
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
1. `SKILL.md` is required for every skill.
|
||||
2. `references/` is the only place for skill-specific supporting docs.
|
||||
3. Nested folders inside `references/` are allowed so a skill can reorganize internals without changing global architecture.
|
||||
4. Skill directories are independent ownership boundaries; no cross-skill file writes.
|
||||
|
||||
## File Placement And Ownership Boundaries
|
||||
|
||||
1. Top-level project docs stay in `docs/*.md`.
|
||||
2. Skill docs stay in `docs/skills/<skill-id>/...`.
|
||||
3. A skill may link to other skills, but must not store content inside another skill's directory.
|
||||
4. Server and runtime code may index and serve docs, but must not be the source of authored markdown.
|
||||
|
||||
## Metadata Location Constraint
|
||||
|
||||
1. Skill metadata is embedded in YAML frontmatter in `SKILL.md`.
|
||||
2. No `metadata.yaml` sidecar exists in the end state.
|
||||
3. Reference lookup metadata, including reference id to relative path mappings, is declared from `SKILL.md` frontmatter rather than inferred as a hidden global convention.
|
||||
|
||||
## Skill Id Contract
|
||||
|
||||
`skill-id` is the public identifier and should satisfy all rules below:
|
||||
|
||||
1. Format: lowercase kebab-case only.
|
||||
2. Character set: `a-z`, `0-9`, and `-`.
|
||||
3. Must start with a letter.
|
||||
4. No underscores, spaces, dots, or uppercase characters.
|
||||
5. Directory name should equal `skill-id` in each committed revision.
|
||||
6. Frontmatter `id` should equal directory name in each committed revision.
|
||||
7. Treat `skill-id` as immutable after release; any rename is a breaking replacement and clients must move to the new id.
|
||||
|
||||
Valid examples:
|
||||
|
||||
1. `fastapi-uv-docker`
|
||||
2. `zensical-docs`
|
||||
3. `pytest-scaffolding`
|
||||
|
||||
Invalid examples:
|
||||
|
||||
1. `fastapi_uv_docker`
|
||||
2. `Zensical-Docs`
|
||||
3. `docs.zensical`
|
||||
|
||||
## Invariants
|
||||
|
||||
This contract guarantees:
|
||||
|
||||
1. One authored source tree in `docs/` for both website and MCP.
|
||||
2. One skill directory maps to one skill identity per revision.
|
||||
3. Namespace and slug drift is minimized by keeping directory and frontmatter ids aligned per revision.
|
||||
4. Per-skill reference structure can evolve without changing cross-skill architecture.
|
||||
5. Packaging for stdio is deterministic because authored content is path-stable.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This contract does not define:
|
||||
|
||||
1. URI versioning policy details.
|
||||
2. The full frontmatter schema.
|
||||
3. Migration instructions from the current architecture.
|
||||
+25
-9
@@ -61,14 +61,20 @@ Use this sequence to confirm behavior:
|
||||
2. fetch only selected skill documents for context
|
||||
3. keep slash commands for deterministic fallback flows
|
||||
|
||||
When resource attachment is unavailable in the active session, use thin catalog discovery tools as operational fallback:
|
||||
When resource attachment is unavailable in the active session, use ResourcesAsTools first, then thin catalog discovery tools as parity fallback:
|
||||
|
||||
1. `search_patterns`
|
||||
2. `get_pattern_by_id`
|
||||
3. `get_skill_document_by_id`
|
||||
1. `list_resources`
|
||||
2. `read_resource`
|
||||
3. `search_patterns`
|
||||
4. `get_pattern_by_id`
|
||||
5. `get_skill_document_by_id`
|
||||
|
||||
The first two are generated from the canonical resource surface and should be preferred in tool-only clients.
|
||||
|
||||
These should stay read-only, minimal, and schema-aligned with catalog resources.
|
||||
|
||||
For very large tool catalogs, server operators can optionally enable tool search mode (`regex` or `bm25`) while keeping `list_resources` and `read_resource` pinned as always-visible fallback tools.
|
||||
|
||||
## What To Type In Copilot Chat
|
||||
|
||||
Use prompts that tell Copilot which MCP feature path to take.
|
||||
@@ -91,7 +97,15 @@ I attached personal-mcp catalog resources first. Use them to identify the best m
|
||||
|
||||
### If only tools are available
|
||||
|
||||
Ask Copilot to explicitly use the catalog tools.
|
||||
Ask Copilot to explicitly use resource-backed tools first.
|
||||
|
||||
Example resource-backed prompt:
|
||||
|
||||
```text
|
||||
Use personal-mcp tool fallback by first calling list_resources, then read_resource for resource://catalog/skills_index and the selected resource://skills/<skill-id>/document URI. Use only that loaded skill context in your answer.
|
||||
```
|
||||
|
||||
If needed, use the thin catalog tools.
|
||||
|
||||
Example discovery prompt:
|
||||
|
||||
@@ -128,7 +142,7 @@ When a task may benefit from personal-mcp skills, use this sequence:
|
||||
|
||||
1. Start with personal-mcp catalog discovery when the task appears to match documented implementation patterns.
|
||||
2. Prefer MCP resources when the chat surface exposes resource attachment.
|
||||
3. If MCP resource attachment is unavailable, use catalog tools instead.
|
||||
3. If MCP resource attachment is unavailable, use `list_resources`/`read_resource` first, then thin catalog tools if needed.
|
||||
4. Load only the most relevant skill document or at most 2 skill documents.
|
||||
5. Treat skill documents as guidance, then reconcile them with the actual repository code before making changes.
|
||||
|
||||
@@ -141,9 +155,11 @@ Preferred discovery order:
|
||||
|
||||
Tool fallback order:
|
||||
|
||||
1. `search_patterns`
|
||||
2. `get_pattern_by_id`
|
||||
3. `get_skill_document_by_id`
|
||||
1. `list_resources`
|
||||
2. `read_resource`
|
||||
3. `search_patterns`
|
||||
4. `get_pattern_by_id`
|
||||
5. `get_skill_document_by_id`
|
||||
|
||||
If confidence is low after catalog discovery, ask one clarifying question before loading more skill documents.
|
||||
```
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
---
|
||||
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: <skill-id>
|
||||
description: <what this skill does and when to use it>
|
||||
|
||||
# Optional Anthropic and Agent Skills fields
|
||||
when_to_use: <extra trigger guidance>
|
||||
allowed-tools: <space-separated string or YAML list>
|
||||
disable-model-invocation: false
|
||||
user-invocable: true
|
||||
license: <optional>
|
||||
compatibility: <optional>
|
||||
|
||||
# Repository-specific metadata
|
||||
x-personal-mcp:
|
||||
id: <skill-id>
|
||||
version: <semver>
|
||||
tags:
|
||||
- <tag>
|
||||
capabilities:
|
||||
- resource://skills/<skill-id>/document
|
||||
depends_on: []
|
||||
# Optional: overrides and nested references only.
|
||||
# Top-level references/*.md are auto-discovered.
|
||||
references:
|
||||
<ref-id>:
|
||||
path: references/<file>.md
|
||||
mime_type: text/markdown
|
||||
title: <short 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` for overrides and nested entries.
|
||||
|
||||
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. Top-level files under `references/*.md` are auto-discovered with `ref-id` derived from a normalized filename stem (lowercase kebab-case).
|
||||
4. Nested folders under `references/` are not auto-discovered and must be declared explicitly.
|
||||
5. `mime_type` defaults to `text/markdown` when omitted.
|
||||
6. `title` is an optional display label.
|
||||
7. 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.
|
||||
@@ -39,6 +39,9 @@ When the server is running, the health check is available at `/healthz` and the
|
||||
## Architecture
|
||||
|
||||
- [Resource-First Pattern Module Architecture](./architecture.md)
|
||||
- [Content Contract](./content.md)
|
||||
- [Frontmatter Contract](./frontmatter.md)
|
||||
- [URI Contract](./uris.md)
|
||||
- [Static Docs Hosting Pattern](./mcp_layout.md)
|
||||
- [Skill Usage Mechanics](./usage.md)
|
||||
- [Copilot MCP Mechanics](./copilot.md)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# FastMCP Greenfield Contracts (Steps 1-5)
|
||||
|
||||
## Step 1: End-State Content Contract
|
||||
|
||||
1. All authored markdown lives under docs/.
|
||||
2. Skill docs live under docs/skills/<skill-id>/.
|
||||
3. Canonical skill shape is:
|
||||
|
||||
```text
|
||||
docs/
|
||||
skills/
|
||||
<skill-id>/
|
||||
SKILL.md
|
||||
references/
|
||||
```
|
||||
|
||||
4. SKILL.md is required for every skill.
|
||||
5. references/ is the only place for skill-specific supporting docs.
|
||||
6. Skill directories are ownership boundaries.
|
||||
7. Skill id rules:
|
||||
- lowercase kebab-case
|
||||
- starts with a letter
|
||||
- directory name matches skill id
|
||||
- SKILL frontmatter id matches directory name
|
||||
|
||||
## Step 2: SKILL Frontmatter and Metadata Contract
|
||||
|
||||
1. name and description are required top-level frontmatter fields.
|
||||
2. Repository indexing metadata lives in x-personal-mcp.
|
||||
3. x-personal-mcp fields:
|
||||
- required: id, version, capabilities
|
||||
- optional: tags, depends_on, references
|
||||
4. references maps stable ref ids to skill-relative markdown paths under references/.
|
||||
5. metadata.yaml sidecars are not part of the canonical model.
|
||||
|
||||
## Step 3: URI Contract and Compatibility Policy
|
||||
|
||||
Canonical URI surface:
|
||||
|
||||
1. resource://catalog/skills_index
|
||||
2. resource://catalog/skills/{skill_id}
|
||||
3. resource://skills/{skill_id}/document
|
||||
4. resource://skills/{skill_id}/references/{ref_id}
|
||||
5. resource://docs/{path*}
|
||||
|
||||
Rules:
|
||||
|
||||
1. skill_id and ref_id are lowercase kebab-case.
|
||||
2. docs path is markdown-only and cannot traverse outside docs/.
|
||||
3. URI families are unversioned and canonical in this phase.
|
||||
4. Breaking changes use direct replacement with no compatibility aliases.
|
||||
|
||||
## Step 4: Docs Registry Loader Contract
|
||||
|
||||
1. Loader uses importlib.resources.files(...) and Traversable APIs.
|
||||
2. Startup validates SKILL frontmatter schema, id invariants, reference integrity, dependency graph, and URI uniqueness.
|
||||
3. Registry is immutable for request-time reads.
|
||||
4. Invalid docs state is a hard startup error.
|
||||
|
||||
## Step 5: Registry-Driven Resource Registration Contract
|
||||
|
||||
1. FastMCP resources are registered from the validated registry.
|
||||
2. RFC6570 templates are used for parameterized routes.
|
||||
3. Docs resources declare explicit MIME types.
|
||||
4. Docs resources include readOnlyHint and idempotentHint annotations.
|
||||
5. Duplicate registrations fail startup via strict duplicate policy.
|
||||
+7
-3
@@ -1,3 +1,7 @@
|
||||
---
|
||||
icon: lucide/server
|
||||
---
|
||||
|
||||
# Static Docs Hosting Pattern
|
||||
|
||||
## Purpose
|
||||
@@ -29,8 +33,10 @@ treeView-beta
|
||||
"docs"
|
||||
"index.md"
|
||||
"architecture.md"
|
||||
"content.md"
|
||||
"frontmatter.md"
|
||||
"mcp_layout.md"
|
||||
"mcp_contract_steps_1_5.md"
|
||||
"uris.md"
|
||||
"skills"
|
||||
"new-skill"
|
||||
"SKILL.md"
|
||||
@@ -153,8 +159,6 @@ When clients cannot attach MCP resources directly, thin catalog tools may retrie
|
||||
|
||||
## URI Compatibility Policy
|
||||
|
||||
This phase is a greenfield break-and-replace baseline.
|
||||
|
||||
1. Canonical URIs are the only supported URIs in this runtime.
|
||||
2. No backward-compatibility aliases or dual registration paths are maintained.
|
||||
3. Contract changes should update clients to canonical URIs directly.
|
||||
|
||||
+16
-6
@@ -1,8 +1,14 @@
|
||||
---
|
||||
icon: lucide/file-plus
|
||||
---
|
||||
|
||||
# Hooking Up a New Skill
|
||||
|
||||
Use this checklist to add a new skill in the Phase 1 docs-first model.
|
||||
Use this checklist to add a new skill in the docs-first model.
|
||||
|
||||
## Step 1 Contract: Canonical Skill Shape
|
||||
For the full contract details, see [Content Contract](./content.md), [Frontmatter Contract](./frontmatter.md), and [URI Contract](./uris.md).
|
||||
|
||||
## Canonical Skill Shape
|
||||
|
||||
Create one skill directory under `docs/skills/`:
|
||||
|
||||
@@ -22,7 +28,7 @@ Rules:
|
||||
3. Skill directories are ownership boundaries; no cross-skill writes.
|
||||
4. `skill-id` is lowercase kebab-case and should remain stable.
|
||||
|
||||
## Step 2 Contract: SKILL.md Frontmatter
|
||||
## SKILL.md Frontmatter
|
||||
|
||||
`SKILL.md` frontmatter is authoritative for metadata.
|
||||
|
||||
@@ -58,6 +64,7 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/<skill-id>/document
|
||||
depends_on: []
|
||||
# Optional: only for nested references or metadata overrides.
|
||||
references:
|
||||
<ref-id>:
|
||||
path: references/<file>.md
|
||||
@@ -70,11 +77,13 @@ Reference manifest rules:
|
||||
|
||||
1. `ref-id` is lowercase kebab-case.
|
||||
2. `path` is skill-relative and must stay under `references/`.
|
||||
3. Reference paths are markdown files.
|
||||
3. Top-level `references/*.md` files are auto-discovered, and `ref-id` is derived from a normalized filename stem.
|
||||
4. Nested `references/**` markdown files must be declared explicitly.
|
||||
5. Reference paths are markdown files.
|
||||
|
||||
No `metadata.yaml` sidecar is part of this model.
|
||||
|
||||
## Step 3 Contract: URI Surface
|
||||
## URI Surface
|
||||
|
||||
Canonical resource URIs for a skill:
|
||||
|
||||
@@ -102,7 +111,8 @@ Compatibility rule:
|
||||
3. Populate frontmatter with `name`, `description`, and `x-personal-mcp` metadata.
|
||||
4. Ensure `x-personal-mcp.id` equals `name` and directory `<skill-id>`.
|
||||
5. Ensure `capabilities` includes `resource://skills/<skill-id>/document`.
|
||||
6. If references are exposed, declare each `ref-id` in `x-personal-mcp.references`.
|
||||
6. Add supporting docs under `references/`; top-level markdown files are exposed automatically.
|
||||
7. Declare `x-personal-mcp.references` only for nested paths or to override defaults.
|
||||
|
||||
## Quick Validation
|
||||
|
||||
|
||||
@@ -22,11 +22,6 @@ x-personal-mcp:
|
||||
depends_on:
|
||||
- new-skill
|
||||
- zensical-docs
|
||||
references:
|
||||
vscode-customization:
|
||||
path: references/vscode-customization.md
|
||||
mime_type: text/markdown
|
||||
title: VS Code Customization
|
||||
---
|
||||
|
||||
# Copilot Customization
|
||||
|
||||
@@ -13,35 +13,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/fastapi-async-sqlalchemy-modernization/document
|
||||
depends_on: []
|
||||
references:
|
||||
index:
|
||||
path: references/index.md
|
||||
mime_type: text/markdown
|
||||
title: Index
|
||||
engine:
|
||||
path: references/engine.md
|
||||
mime_type: text/markdown
|
||||
title: Engine
|
||||
session:
|
||||
path: references/session.md
|
||||
mime_type: text/markdown
|
||||
title: Session
|
||||
transactions:
|
||||
path: references/transactions.md
|
||||
mime_type: text/markdown
|
||||
title: Transactions
|
||||
implicit-io:
|
||||
path: references/implicit_io.md
|
||||
mime_type: text/markdown
|
||||
title: Implicit IO
|
||||
observability:
|
||||
path: references/observability.md
|
||||
mime_type: text/markdown
|
||||
title: Observability
|
||||
template:
|
||||
path: references/template.md
|
||||
mime_type: text/markdown
|
||||
title: Template
|
||||
---
|
||||
|
||||
# FastAPI Async SQLAlchemy Modernization Plan
|
||||
|
||||
@@ -12,23 +12,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/fastapi-uv-docker/document
|
||||
depends_on: []
|
||||
references:
|
||||
fastapi-best-practices:
|
||||
path: references/fastapi-best-practices.md
|
||||
mime_type: text/markdown
|
||||
title: FastAPI Best Practices
|
||||
uv-project-layout:
|
||||
path: references/uv-project-layout.md
|
||||
mime_type: text/markdown
|
||||
title: uv Project Layout
|
||||
uvicorn-settings:
|
||||
path: references/uvicorn-settings.md
|
||||
mime_type: text/markdown
|
||||
title: Uvicorn Settings
|
||||
docker-cloud-native:
|
||||
path: references/docker-cloud-native.md
|
||||
mime_type: text/markdown
|
||||
title: Docker Cloud Native
|
||||
---
|
||||
|
||||
# FastAPI Project Best Practices
|
||||
|
||||
@@ -13,19 +13,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/nicegui-ui-customization/document
|
||||
depends_on: []
|
||||
references:
|
||||
architecture-and-styling:
|
||||
path: references/architecture-and-styling.md
|
||||
mime_type: text/markdown
|
||||
title: Architecture and Styling
|
||||
interaction-patterns:
|
||||
path: references/interaction-patterns.md
|
||||
mime_type: text/markdown
|
||||
title: Interaction Patterns
|
||||
troubleshooting-and-quality-gates:
|
||||
path: references/troubleshooting-and-quality-gates.md
|
||||
mime_type: text/markdown
|
||||
title: Troubleshooting and Quality Gates
|
||||
---
|
||||
|
||||
# NiceGUI UI Customization Workflow
|
||||
|
||||
@@ -13,15 +13,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/nicegui/document
|
||||
depends_on: []
|
||||
references:
|
||||
architecture:
|
||||
path: references/architecture.md
|
||||
mime_type: text/markdown
|
||||
title: Architecture
|
||||
source-documentation:
|
||||
path: references/source-documentation.md
|
||||
mime_type: text/markdown
|
||||
title: Source Documentation
|
||||
---
|
||||
|
||||
# NiceGUI
|
||||
|
||||
@@ -12,19 +12,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/pytest-scaffolding/document
|
||||
depends_on: []
|
||||
references:
|
||||
pytest-docs:
|
||||
path: references/pytest-docs.md
|
||||
mime_type: text/markdown
|
||||
title: Pytest Docs
|
||||
fastapi-testing:
|
||||
path: references/fastapi-testing.md
|
||||
mime_type: text/markdown
|
||||
title: FastAPI Testing
|
||||
sqlalchemy-testing:
|
||||
path: references/sqlalchemy-testing.md
|
||||
mime_type: text/markdown
|
||||
title: SQLAlchemy Testing
|
||||
---
|
||||
|
||||
# Pytest Scaffolding
|
||||
|
||||
@@ -12,11 +12,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/python-logging-dictconfig/document
|
||||
depends_on: []
|
||||
references:
|
||||
python-logging-docs:
|
||||
path: references/python-logging-docs.md
|
||||
mime_type: text/markdown
|
||||
title: Python Logging Docs
|
||||
---
|
||||
|
||||
# Idiomatic Python Logging with dictConfig
|
||||
|
||||
@@ -16,19 +16,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/vscode-configuration/document
|
||||
depends_on: []
|
||||
references:
|
||||
debug-launch-configurations:
|
||||
path: references/debug-launch-configurations.md
|
||||
mime_type: text/markdown
|
||||
title: Debug Launch Configurations
|
||||
fastapi-debugpy-launch:
|
||||
path: references/fastapi-debugpy-launch.md
|
||||
mime_type: text/markdown
|
||||
title: FastAPI Debugpy Launch
|
||||
tasks-json-configuration:
|
||||
path: references/tasks-json-configuration.md
|
||||
mime_type: text/markdown
|
||||
title: Tasks JSON Configuration
|
||||
---
|
||||
|
||||
# VS Code Configuration
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: zensical-docs
|
||||
description: 'Reference skill for Zensical documentation mechanics. Use for quick lookup of docs structure, feature options, and source links, then edit this skill over time to record project preferences for when each feature should be used.'
|
||||
description: 'Reference skill for Zensical documentation mechanics. Use for quick lookup of docs structure, feature options, and source links. Prefer inline Markdown links to source docs and avoid bare URLs because this content is rendered as human docs and MCP resources.'
|
||||
argument-hint: 'What are you documenting, who is the audience, and what Zensical features are in scope?'
|
||||
x-personal-mcp:
|
||||
id: zensical-docs
|
||||
@@ -20,31 +20,6 @@ x-personal-mcp:
|
||||
capabilities:
|
||||
- resource://skills/zensical-docs/document
|
||||
depends_on: []
|
||||
references:
|
||||
index:
|
||||
path: references/index.md
|
||||
mime_type: text/markdown
|
||||
title: Source Map
|
||||
zensical-features:
|
||||
path: references/zensical-features.md
|
||||
mime_type: text/markdown
|
||||
title: Feature Catalog
|
||||
theme-customization-and-icons:
|
||||
path: references/theme-customization-and-icons.md
|
||||
mime_type: text/markdown
|
||||
title: Theme Customization and Icons
|
||||
documentation-quality:
|
||||
path: references/documentation-quality.md
|
||||
mime_type: text/markdown
|
||||
title: Documentation Quality
|
||||
discoverability-and-ia:
|
||||
path: references/discoverability-and-ia.md
|
||||
mime_type: text/markdown
|
||||
title: Discoverability and IA
|
||||
code-heavy-docs-and-mkdocstrings:
|
||||
path: references/code-heavy-docs-and-mkdocstrings.md
|
||||
mime_type: text/markdown
|
||||
title: Code-Heavy Docs and Mkdocstrings
|
||||
---
|
||||
|
||||
# Zensical Documentation Authoring
|
||||
@@ -124,6 +99,30 @@ Keep this section short and revise it over time.
|
||||
|
||||
When making a recommendation, link back to the relevant reference file first, and when possible to the upstream docs linked from that reference.
|
||||
|
||||
## Link Formatting Rule
|
||||
|
||||
Because this project publishes the same markdown for both `/docs` and MCP resources, link quality is part of the content contract.
|
||||
|
||||
- Never leave a bare URL in prose or list items.
|
||||
- Prefer using in-place Markdown links with meaningful labels.
|
||||
- For external sources, prefer `[descriptive label](https://...)` over raw `https://...`.
|
||||
- For internal files, prefer relative Markdown links so rendered docs remain navigable.
|
||||
- Any mention of a library or a specific library feature should include a link to source documentation somewhere on the page.
|
||||
- If inline linking is awkward or the citation payload is too large, use a footnote or tooltip citation instead.
|
||||
|
||||
Example preferred style:
|
||||
|
||||
- `See [importlib.resources](https://docs.python.org/3/library/importlib.resources.html) for packaging details.`
|
||||
|
||||
Example to avoid:
|
||||
|
||||
- `See https://docs.python.org/3/library/importlib.resources.html for packaging details.`
|
||||
|
||||
Acceptable alternatives when inline links are not ideal:
|
||||
|
||||
- Add a footnote-style source citation at the end of the section or page.
|
||||
- Add a tooltip citation when the docs pattern supports it.
|
||||
|
||||
## Compatibility Rule
|
||||
|
||||
Prefer the Zensical-native way of doing something when it exists and is well-supported.
|
||||
@@ -136,3 +135,4 @@ Return only what is useful for the current docs task:
|
||||
1. Which reference to read next.
|
||||
2. The smallest recommended docs or config change.
|
||||
3. Any repo-specific preference this suggests should be added back into this skill.
|
||||
4. For any library or feature-level claim, include a source-doc citation somewhere (inline link preferred; footnote or tooltip acceptable).
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
---
|
||||
icon: lucide/link
|
||||
---
|
||||
|
||||
# URI Contract
|
||||
|
||||
This page defines the canonical resource URI contract, template parameter rules, and compatibility policy.
|
||||
|
||||
## Canonical URI Surface
|
||||
|
||||
The public, preferred URIs are:
|
||||
|
||||
1. `resource://catalog/skills_index`
|
||||
2. `resource://catalog/skills/{skill_id}`
|
||||
3. `resource://skills/{skill_id}/document`
|
||||
4. `resource://skills/{skill_id}/references/{ref_id}`
|
||||
5. `resource://docs/{path*}`
|
||||
|
||||
Contract intent:
|
||||
|
||||
1. Catalog URIs are discovery surfaces.
|
||||
2. Skill URIs are the primary per-skill guidance surfaces.
|
||||
3. The docs wildcard URI is a direct authored-markdown access surface under `docs/`.
|
||||
|
||||
## URI Semantics
|
||||
|
||||
### `resource://catalog/skills_index`
|
||||
|
||||
1. Returns a compact list of skill records for discovery.
|
||||
2. Contains one entry per `skill_id`.
|
||||
3. Includes enough metadata for client-side selection, at minimum `id`, `name`, `description`, `tags`, and `capabilities`.
|
||||
|
||||
### `resource://catalog/skills/{skill_id}`
|
||||
|
||||
1. Returns one normalized record for `skill_id`.
|
||||
2. Includes the canonical document URI and declared reference ids.
|
||||
3. Returns not found when `skill_id` does not exist.
|
||||
|
||||
### `resource://skills/{skill_id}/document`
|
||||
|
||||
1. Returns the canonical `SKILL.md` authored content for that skill.
|
||||
2. `skill_id` must satisfy the stable skill id rules from the content contract.
|
||||
|
||||
### `resource://skills/{skill_id}/references/{ref_id}`
|
||||
|
||||
1. Returns one reference document declared in the skill frontmatter references manifest.
|
||||
2. `ref_id` is the stable public handle for that reference document.
|
||||
|
||||
### `resource://docs/{path*}`
|
||||
|
||||
1. Returns authored markdown at a normalized relative path under `docs/`.
|
||||
2. Supports nested paths via RFC6570 wildcard expansion.
|
||||
3. Typical examples include `index.md`, `usage.md`, `skills/<skill-id>/SKILL.md`, and `skills/<skill-id>/references/<file>.md`.
|
||||
|
||||
## Template Parameter And Validation Rules
|
||||
|
||||
### `skill_id`
|
||||
|
||||
1. Lowercase kebab-case.
|
||||
2. Must satisfy the stable skill id rules from the content contract.
|
||||
|
||||
### `ref_id`
|
||||
|
||||
1. Lowercase kebab-case.
|
||||
2. Must be declared in the skill's references manifest.
|
||||
|
||||
### `path*`
|
||||
|
||||
1. Relative POSIX path only.
|
||||
2. No leading slash.
|
||||
3. No `..` traversal segments.
|
||||
4. Resolves only inside `docs/`.
|
||||
5. Markdown-only in the end state, meaning `.md` files.
|
||||
|
||||
## URI Versioning Policy
|
||||
|
||||
Default rule:
|
||||
|
||||
1. Keep URIs unversioned by default.
|
||||
2. Allow URI and payload updates when they improve clarity or implementation simplicity.
|
||||
|
||||
Breaking-change rule:
|
||||
|
||||
1. Breaking changes use direct replacement of the canonical URI family.
|
||||
2. No compatibility aliases or dual URI families are maintained.
|
||||
|
||||
FastMCP version metadata usage:
|
||||
|
||||
1. Resource `version` metadata may be used for implementation and version discovery.
|
||||
2. URI readability and maintainability remain the primary contract.
|
||||
|
||||
## Reference Id Compatibility Policy
|
||||
|
||||
`ref_id` is the public identifier for a reference document, separate from file path.
|
||||
|
||||
Rules:
|
||||
|
||||
1. Prefer keeping `ref_id` stable when practical.
|
||||
2. File paths may change without URI churn as long as the mapped `ref_id` still resolves.
|
||||
3. If a reference is renamed, introduce a new `ref_id` and treat the old one as retired.
|
||||
4. Avoid reusing retired `ref_id` values for unrelated content.
|
||||
|
||||
## Invariants
|
||||
|
||||
This contract guarantees:
|
||||
|
||||
1. One canonical URI pattern per core capability surface.
|
||||
2. Fast, low-friction URI evolution through direct replacement of canonical URIs.
|
||||
3. A single canonical catalog URI family with no alias maintenance overhead.
|
||||
4. Reference mappings can evolve with minimal churn.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This contract does not define:
|
||||
|
||||
1. Implementation-specific transform wiring details, such as `VersionFilter`, mounts, or provider composition.
|
||||
2. Migration script mechanics for auto-generating aliases.
|
||||
3. Authorization policy design for URI-level access control.
|
||||
+25
-7
@@ -72,7 +72,7 @@ In this project, "automatic loading" should be read as a preference you express
|
||||
In practice, there are two reliable ways to make skill content available in chat:
|
||||
|
||||
1. explicit resource attachment through `Add Context > MCP Resources` or `MCP: Browse Resources`
|
||||
2. MCP tool invocation such as `search_patterns` followed by `get_skill_document_by_id`
|
||||
2. MCP tool invocation using `list_resources`/`read_resource` (ResourcesAsTools), with thin catalog tools as parity fallback
|
||||
|
||||
Instruction quality and metadata quality still matter, because they influence whether Copilot recognizes that the MCP server is relevant and chooses the tool path well.
|
||||
|
||||
@@ -146,6 +146,21 @@ Weak metadata reduces Copilot match quality and increases wrong context injectio
|
||||
|
||||
If you skip the catalog/index step, behavior is less predictable and may either miss relevant skills or pull too much context.
|
||||
|
||||
## Optional Tool Search Mode
|
||||
|
||||
When tool catalogs grow, FastMCP search transforms can reduce tool-list noise for tool-only clients.
|
||||
|
||||
Runtime switches:
|
||||
|
||||
1. `PERSONAL_MCP_TOOL_SEARCH=none|regex|bm25` (default `none`)
|
||||
2. `PERSONAL_MCP_TOOL_SEARCH_MAX_RESULTS=<positive int>` (default `5`)
|
||||
|
||||
Behavior:
|
||||
|
||||
1. `regex` uses deterministic regex matching for targeted queries.
|
||||
2. `bm25` uses ranked natural-language matching.
|
||||
3. `list_resources` and `read_resource` stay visible so resource-backed fallback remains primary.
|
||||
|
||||
## Copilot Instruction Pattern
|
||||
|
||||
If you want Copilot to use `personal-mcp` skill content more reliably, the instruction file should describe three things clearly:
|
||||
@@ -170,7 +185,7 @@ When a task may match a documented implementation pattern from `personal-mcp`:
|
||||
|
||||
1. Start with catalog-first discovery.
|
||||
2. Prefer MCP resources when the chat surface exposes resource attachment.
|
||||
3. If MCP resource attachment is unavailable, use catalog tools instead.
|
||||
3. If MCP resource attachment is unavailable, use `list_resources`/`read_resource` first, then thin catalog tools if needed.
|
||||
4. Load only the most relevant skill document, or at most 2 skill documents.
|
||||
5. Reconcile loaded skill guidance with the actual repository code before making changes.
|
||||
|
||||
@@ -183,9 +198,11 @@ Preferred resource order:
|
||||
|
||||
Preferred tool fallback order:
|
||||
|
||||
1. `search_patterns`
|
||||
2. `get_pattern_by_id`
|
||||
3. `get_skill_document_by_id`
|
||||
1. `list_resources`
|
||||
2. `read_resource`
|
||||
3. `search_patterns`
|
||||
4. `get_pattern_by_id`
|
||||
5. `get_skill_document_by_id`
|
||||
|
||||
If confidence is low after discovery, ask one clarifying question before loading more context.
|
||||
```
|
||||
@@ -225,8 +242,9 @@ Suggested instruction policy text:
|
||||
|
||||
1. Start with catalog-first discovery.
|
||||
2. Prefer MCP resources when the chat surface exposes resource attachment.
|
||||
3. Otherwise use catalog tools to search and load one or two likely skill documents.
|
||||
4. If confidence is low, ask one clarifying question before loading more.
|
||||
3. Otherwise use tool fallback to load one or two likely skill documents.
|
||||
4. Prefer `list_resources`/`read_resource` first when operating in tool-only clients.
|
||||
5. If confidence is low, ask one clarifying question before loading more.
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
@@ -20,3 +20,9 @@ build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/personal_mcp"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pre-commit>=4.6.0",
|
||||
"ruff>=0.15.18",
|
||||
]
|
||||
|
||||
@@ -4,6 +4,8 @@ import os
|
||||
from typing import Any
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import ResourcesAsTools
|
||||
from fastmcp.server.transforms.search import BM25SearchTransform, RegexSearchTransform
|
||||
|
||||
from personal_mcp.catalog.server import (
|
||||
build_skill_detail_payload,
|
||||
@@ -20,6 +22,8 @@ from personal_mcp.skills.document_loader import (
|
||||
)
|
||||
|
||||
DOCS_ROOT = os.getenv("PERSONAL_MCP_DOCS_ROOT", "../../docs")
|
||||
TOOL_SEARCH_MODE = os.getenv("PERSONAL_MCP_TOOL_SEARCH", "none").strip().lower()
|
||||
TOOL_SEARCH_MAX_RESULTS = os.getenv("PERSONAL_MCP_TOOL_SEARCH_MAX_RESULTS", "5")
|
||||
REGISTRY: DocsRegistry = load_docs_registry(
|
||||
package_anchor="personal_mcp",
|
||||
docs_root=DOCS_ROOT,
|
||||
@@ -28,6 +32,44 @@ REGISTRY: DocsRegistry = load_docs_registry(
|
||||
mcp = FastMCP("personal-mcp", on_duplicate="error")
|
||||
|
||||
|
||||
def _parse_positive_int(value: str, *, env_name: str) -> int:
|
||||
try:
|
||||
parsed = int(value)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"{env_name} must be an integer") from exc
|
||||
if parsed <= 0:
|
||||
raise ValueError(f"{env_name} must be greater than zero")
|
||||
return parsed
|
||||
|
||||
|
||||
def _install_tool_fallback_transforms() -> None:
|
||||
# Expose list_resources/read_resource for tool-only clients.
|
||||
mcp.add_transform(ResourcesAsTools(mcp))
|
||||
|
||||
if TOOL_SEARCH_MODE in {"", "none"}:
|
||||
return
|
||||
|
||||
max_results = _parse_positive_int(
|
||||
TOOL_SEARCH_MAX_RESULTS,
|
||||
env_name="PERSONAL_MCP_TOOL_SEARCH_MAX_RESULTS",
|
||||
)
|
||||
kwargs: dict[str, Any] = {
|
||||
"max_results": max_results,
|
||||
"always_visible": ["list_resources", "read_resource"],
|
||||
}
|
||||
|
||||
if TOOL_SEARCH_MODE == "regex":
|
||||
mcp.add_transform(RegexSearchTransform(**kwargs))
|
||||
return
|
||||
if TOOL_SEARCH_MODE == "bm25":
|
||||
mcp.add_transform(BM25SearchTransform(**kwargs))
|
||||
return
|
||||
|
||||
raise ValueError(
|
||||
"PERSONAL_MCP_TOOL_SEARCH must be one of: none, regex, bm25"
|
||||
)
|
||||
|
||||
|
||||
def _ro_annotations() -> dict[str, bool]:
|
||||
return {
|
||||
"readOnlyHint": True,
|
||||
@@ -141,3 +183,6 @@ def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
|
||||
"found": True,
|
||||
"document": read_skill_document(REGISTRY, skill_id),
|
||||
}
|
||||
|
||||
|
||||
_install_tool_fallback_transforms()
|
||||
|
||||
@@ -258,6 +258,51 @@ def _normalize_docs_path(path: str) -> str:
|
||||
return normalized.as_posix()
|
||||
|
||||
|
||||
def _title_from_reference_filename(filename: str) -> str:
|
||||
stem = PurePosixPath(filename).stem
|
||||
normalized = stem.replace("-", " ").replace("_", " ").split()
|
||||
if not normalized:
|
||||
return stem
|
||||
return " ".join(token.capitalize() for token in normalized)
|
||||
|
||||
|
||||
def _reference_id_from_filename(filename: str) -> str | None:
|
||||
stem = PurePosixPath(filename).stem.strip().lower().replace("_", "-")
|
||||
normalized = re.sub(r"[^a-z0-9-]+", "-", stem)
|
||||
normalized = re.sub(r"-+", "-", normalized).strip("-")
|
||||
if not normalized:
|
||||
return None
|
||||
if not SKILL_ID_RE.fullmatch(normalized):
|
||||
return None
|
||||
return normalized
|
||||
|
||||
|
||||
def _discover_top_level_references(
|
||||
*,
|
||||
skill_dir: Traversable,
|
||||
) -> dict[str, ReferenceEntry]:
|
||||
references_dir = skill_dir.joinpath("references")
|
||||
if not references_dir.is_dir():
|
||||
return {}
|
||||
|
||||
discovered: dict[str, ReferenceEntry] = {}
|
||||
for child in sorted(references_dir.iterdir(), key=lambda item: item.name):
|
||||
if child.is_dir() or not child.is_file():
|
||||
continue
|
||||
if not child.name.lower().endswith(".md"):
|
||||
continue
|
||||
|
||||
ref_id = _reference_id_from_filename(child.name)
|
||||
if ref_id is None:
|
||||
continue
|
||||
|
||||
discovered[ref_id] = ReferenceEntry(
|
||||
path=PurePosixPath("references").joinpath(child.name).as_posix(),
|
||||
title=_title_from_reference_filename(child.name),
|
||||
)
|
||||
return discovered
|
||||
|
||||
|
||||
def _ensure_no_cycles(skills_by_id: dict[str, SkillRecord]) -> list[tuple[str, str]]:
|
||||
visiting: set[str] = set()
|
||||
visited: set[str] = set()
|
||||
@@ -370,8 +415,11 @@ def load_docs_registry(
|
||||
)
|
||||
continue
|
||||
|
||||
effective_reference_entries = _discover_top_level_references(skill_dir=skill_dir)
|
||||
effective_reference_entries.update(frontmatter.x_personal_mcp.references)
|
||||
|
||||
references: dict[str, ReferenceRecord] = {}
|
||||
for ref_id, ref_entry in frontmatter.x_personal_mcp.references.items():
|
||||
for ref_id, ref_entry in effective_reference_entries.items():
|
||||
ref_relpath = skill_rel_root.joinpath(ref_entry.path).as_posix()
|
||||
if ref_relpath not in docs_markdown_by_path:
|
||||
issues.append(
|
||||
|
||||
@@ -178,6 +178,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.4.1"
|
||||
@@ -273,6 +282,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.8.0"
|
||||
@@ -395,6 +413,15 @@ server = [
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.29.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffelib"
|
||||
version = "2.0.2"
|
||||
@@ -486,6 +513,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.18"
|
||||
@@ -756,6 +792,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-pydantic"
|
||||
version = "0.5.1"
|
||||
@@ -807,6 +852,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
{ name = "identify" },
|
||||
{ name = "nodeenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompts"
|
||||
version = "0.1.0"
|
||||
@@ -820,6 +881,12 @@ dependencies = [
|
||||
{ name = "zensical" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pre-commit" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0.115.0" },
|
||||
@@ -830,6 +897,12 @@ requires-dist = [
|
||||
{ name = "zensical", specifier = ">=0.0.45" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pre-commit", specifier = ">=4.6.0" },
|
||||
{ name = "ruff", specifier = ">=0.15.18" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "py-key-value-aio"
|
||||
version = "0.4.5"
|
||||
@@ -1018,6 +1091,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-discovery"
|
||||
version = "1.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
@@ -1260,6 +1346,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secretstorage"
|
||||
version = "3.5.0"
|
||||
@@ -1430,6 +1541,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "21.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "python-discovery" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.2.0"
|
||||
|
||||
+4
-2
@@ -48,9 +48,11 @@ nav = [
|
||||
{ "Home" = "index.md" },
|
||||
{ "Guide" = [
|
||||
{ "Arch" = "architecture.md" },
|
||||
{ "Content" = "content.md" },
|
||||
{ "Frontmatter" = "frontmatter.md" },
|
||||
{ "URIs" = "uris.md" },
|
||||
{ "MCP" = "mcp_layout.md" },
|
||||
{ "Copilot" = "copilot.md" },
|
||||
{ "Contracts 1-5" = "mcp_contract_steps_1_5.md" },
|
||||
{ "Usage" = "usage.md" },
|
||||
{ "Future Work" = "future_work.md" },
|
||||
{ "New Skill" = "new_skill.md" },
|
||||
@@ -92,7 +94,7 @@ nav = [
|
||||
{ "Arch" = "skills/nicegui/references/architecture.md" },
|
||||
{ "Sources" = "skills/nicegui/references/source-documentation.md" },
|
||||
] },
|
||||
{ "NiceGUI UI" = [
|
||||
{ "NiceGUI Fine-Tuning" = [
|
||||
{ "Overview" = "skills/nicegui-ui-customization/SKILL.md" },
|
||||
{ "Style" = "skills/nicegui-ui-customization/references/architecture-and-styling.md" },
|
||||
{ "Flows" = "skills/nicegui-ui-customization/references/interaction-patterns.md" },
|
||||
|
||||
Reference in New Issue
Block a user