Compare commits
2 Commits
3885774e5b
...
bb7508cf65
| Author | SHA1 | Date | |
|---|---|---|---|
| bb7508cf65 | |||
| 467e1d3c35 |
@@ -55,7 +55,7 @@ Rules:
|
|||||||
- No underscores, spaces, dots, or uppercase characters.
|
- No underscores, spaces, dots, or uppercase characters.
|
||||||
- Directory name should equal `skill-id` in each committed revision.
|
- Directory name should equal `skill-id` in each committed revision.
|
||||||
- Frontmatter `id` should equal directory name 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:
|
Example valid ids:
|
||||||
|
|
||||||
|
|||||||
@@ -72,16 +72,6 @@ Contract intent:
|
|||||||
- Resolves only inside `docs/`.
|
- Resolves only inside `docs/`.
|
||||||
- This surface is markdown-only in end state (`.md` files).
|
- 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
|
### URI Versioning Policy
|
||||||
|
|
||||||
Default rule:
|
Default rule:
|
||||||
@@ -91,30 +81,14 @@ Default rule:
|
|||||||
|
|
||||||
Breaking-change rule:
|
Breaking-change rule:
|
||||||
|
|
||||||
- If clients are already using an older shape, provide either:
|
- Breaking changes use direct replacement of the canonical URI family.
|
||||||
- a short-lived alias, or
|
- No compatibility aliases or dual URI families are maintained in this greenfield phase.
|
||||||
- a versioned URI family such as `resource://catalog/v2/...`.
|
|
||||||
- Choose the lightest migration path that minimizes maintenance overhead.
|
|
||||||
|
|
||||||
FastMCP version metadata usage:
|
FastMCP version metadata usage:
|
||||||
|
|
||||||
- Resource `version` metadata MAY be used for implementation/version discovery.
|
- Resource `version` metadata MAY be used for implementation/version discovery.
|
||||||
- URI readability and maintainability remain the primary contract.
|
- 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
|
### Reference ID Compatibility Policy
|
||||||
|
|
||||||
`ref_id` is the public identifier for a reference document, separate from file path.
|
`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.
|
- Prefer keeping `ref_id` stable when practical.
|
||||||
- File paths may change without URI churn as long as the mapped `ref_id` resolves.
|
- 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.
|
- 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
|
### Invariants This Contract Guarantees
|
||||||
|
|
||||||
- One canonical URI pattern per core capability surface.
|
- One canonical URI pattern per core capability surface.
|
||||||
- Fast, low-friction URI evolution with optional compatibility aliases.
|
- Fast, low-friction URI evolution through direct replacement of canonical URIs.
|
||||||
- Explicit migration path for catalog URI consolidation when needed.
|
- A single canonical catalog URI family with no alias maintenance overhead.
|
||||||
- Reference mappings can evolve with minimal churn.
|
- Reference mappings can evolve with minimal churn.
|
||||||
|
|
||||||
### Non-goals For Step 3
|
### Non-goals For Step 3
|
||||||
|
|||||||
@@ -50,17 +50,19 @@ Rules:
|
|||||||
|
|
||||||
### Tool Fallback Surface (Normative)
|
### Tool Fallback Surface (Normative)
|
||||||
|
|
||||||
The minimal read-only fallback tool set is:
|
The fallback tool surface includes:
|
||||||
|
|
||||||
1. `search_patterns`
|
1. `list_resources`
|
||||||
2. `get_pattern_by_id`
|
2. `read_resource`
|
||||||
3. `get_skill_document_by_id`
|
3. `search_patterns`
|
||||||
|
4. `get_pattern_by_id`
|
||||||
|
5. `get_skill_document_by_id`
|
||||||
|
|
||||||
Fallback order:
|
Fallback order:
|
||||||
|
|
||||||
1. call `search_patterns` to find likely skills
|
1. call `list_resources` to inspect canonical static/template resource surfaces
|
||||||
2. call `get_pattern_by_id` for one selected id when detail is needed
|
2. call `read_resource` for catalog URIs and selected skill URIs
|
||||||
3. call `get_skill_document_by_id` only for selected skill(s)
|
3. use thin catalog tools only when additional metadata-first narrowing is needed
|
||||||
|
|
||||||
Tool behavior requirements:
|
Tool behavior requirements:
|
||||||
|
|
||||||
@@ -71,14 +73,14 @@ Tool behavior requirements:
|
|||||||
|
|
||||||
### Resources-As-Tools Compatibility Layer
|
### 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:
|
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.
|
2. It preserves canonical URIs and metadata semantics.
|
||||||
3. It does not replace the minimal catalog tools listed above.
|
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
|
### Resource/Tool Parity Contract
|
||||||
|
|
||||||
@@ -144,10 +146,11 @@ Separation-of-concerns rule:
|
|||||||
Step 6 is complete when all are true:
|
Step 6 is complete when all are true:
|
||||||
|
|
||||||
1. Resource-first discovery remains the documented and implemented default path.
|
1. Resource-first discovery remains the documented and implemented default path.
|
||||||
2. The fallback tool set is minimal, read-only, and schema-aligned.
|
2. `list_resources` and `read_resource` are available for tool-only clients.
|
||||||
3. Fallback tool outputs map to canonical skill identities and URIs.
|
3. Thin catalog tools remain minimal, read-only parity surfaces.
|
||||||
4. Context loading is bounded and clarifying-question behavior is documented for low-confidence cases.
|
4. Fallback tool outputs map to canonical skill identities and URIs.
|
||||||
5. No second content source is introduced; resources and tools resolve the same authored markdown.
|
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
|
### Non-goals for Step 6
|
||||||
|
|
||||||
|
|||||||
+15
-5
@@ -16,11 +16,17 @@ The system is complete in three layers:
|
|||||||
2. Catalog resources provide normalized discovery.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
### 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:
|
Loader responsibilities:
|
||||||
|
|
||||||
@@ -104,6 +110,8 @@ flowchart TD
|
|||||||
|
|
||||||
Each skill declares frontmatter in `docs/skills/<skill-id>/SKILL.md`.
|
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:
|
Anthropic-facing required fields:
|
||||||
|
|
||||||
1. name
|
1. name
|
||||||
@@ -124,6 +132,8 @@ No `metadata.yaml` sidecar is part of the end-state contract.
|
|||||||
|
|
||||||
Canonical resource URIs are:
|
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
|
1. resource://skills/<skill_id>/document
|
||||||
2. resource://skills/<skill_id>/references/<ref_id>
|
2. resource://skills/<skill_id>/references/<ref_id>
|
||||||
3. resource://catalog/skills_index
|
3. resource://catalog/skills_index
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# 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
|
2. fetch only selected skill documents for context
|
||||||
3. keep slash commands for deterministic fallback flows
|
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`
|
1. `list_resources`
|
||||||
2. `get_pattern_by_id`
|
2. `read_resource`
|
||||||
3. `get_skill_document_by_id`
|
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.
|
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
|
## What To Type In Copilot Chat
|
||||||
|
|
||||||
Use prompts that tell Copilot which MCP feature path to take.
|
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
|
### 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:
|
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.
|
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.
|
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.
|
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.
|
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:
|
Tool fallback order:
|
||||||
|
|
||||||
1. `search_patterns`
|
1. `list_resources`
|
||||||
2. `get_pattern_by_id`
|
2. `read_resource`
|
||||||
3. `get_skill_document_by_id`
|
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.
|
If confidence is low after catalog discovery, ask one clarifying question before loading more skill documents.
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
# 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: []
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -39,6 +39,9 @@ When the server is running, the health check is available at `/healthz` and the
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- [Resource-First Pattern Module Architecture](./architecture.md)
|
- [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)
|
- [Static Docs Hosting Pattern](./mcp_layout.md)
|
||||||
- [Skill Usage Mechanics](./usage.md)
|
- [Skill Usage Mechanics](./usage.md)
|
||||||
- [Copilot MCP Mechanics](./copilot.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.
|
|
||||||
+3
-3
@@ -29,8 +29,10 @@ treeView-beta
|
|||||||
"docs"
|
"docs"
|
||||||
"index.md"
|
"index.md"
|
||||||
"architecture.md"
|
"architecture.md"
|
||||||
|
"content.md"
|
||||||
|
"frontmatter.md"
|
||||||
"mcp_layout.md"
|
"mcp_layout.md"
|
||||||
"mcp_contract_steps_1_5.md"
|
"uris.md"
|
||||||
"skills"
|
"skills"
|
||||||
"new-skill"
|
"new-skill"
|
||||||
"SKILL.md"
|
"SKILL.md"
|
||||||
@@ -153,8 +155,6 @@ When clients cannot attach MCP resources directly, thin catalog tools may retrie
|
|||||||
|
|
||||||
## URI Compatibility Policy
|
## URI Compatibility Policy
|
||||||
|
|
||||||
This phase is a greenfield break-and-replace baseline.
|
|
||||||
|
|
||||||
1. Canonical URIs are the only supported URIs in this runtime.
|
1. Canonical URIs are the only supported URIs in this runtime.
|
||||||
2. No backward-compatibility aliases or dual registration paths are maintained.
|
2. No backward-compatibility aliases or dual registration paths are maintained.
|
||||||
3. Contract changes should update clients to canonical URIs directly.
|
3. Contract changes should update clients to canonical URIs directly.
|
||||||
|
|||||||
+6
-4
@@ -1,8 +1,10 @@
|
|||||||
# Hooking Up a New Skill
|
# 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/`:
|
Create one skill directory under `docs/skills/`:
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ Rules:
|
|||||||
3. Skill directories are ownership boundaries; no cross-skill writes.
|
3. Skill directories are ownership boundaries; no cross-skill writes.
|
||||||
4. `skill-id` is lowercase kebab-case and should remain stable.
|
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.
|
`SKILL.md` frontmatter is authoritative for metadata.
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ Reference manifest rules:
|
|||||||
|
|
||||||
No `metadata.yaml` sidecar is part of this model.
|
No `metadata.yaml` sidecar is part of this model.
|
||||||
|
|
||||||
## Step 3 Contract: URI Surface
|
## URI Surface
|
||||||
|
|
||||||
Canonical resource URIs for a skill:
|
Canonical resource URIs for a skill:
|
||||||
|
|
||||||
|
|||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
# 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:
|
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`
|
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.
|
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.
|
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
|
## Copilot Instruction Pattern
|
||||||
|
|
||||||
If you want Copilot to use `personal-mcp` skill content more reliably, the instruction file should describe three things clearly:
|
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.
|
1. Start with catalog-first discovery.
|
||||||
2. Prefer MCP resources when the chat surface exposes resource attachment.
|
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.
|
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.
|
5. Reconcile loaded skill guidance with the actual repository code before making changes.
|
||||||
|
|
||||||
@@ -183,9 +198,11 @@ Preferred resource order:
|
|||||||
|
|
||||||
Preferred tool fallback order:
|
Preferred tool fallback order:
|
||||||
|
|
||||||
1. `search_patterns`
|
1. `list_resources`
|
||||||
2. `get_pattern_by_id`
|
2. `read_resource`
|
||||||
3. `get_skill_document_by_id`
|
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.
|
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.
|
1. Start with catalog-first discovery.
|
||||||
2. Prefer MCP resources when the chat surface exposes resource attachment.
|
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.
|
3. Otherwise use tool fallback to load one or two likely skill documents.
|
||||||
4. If confidence is low, ask one clarifying question before loading more.
|
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
|
## Summary
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import os
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
|
from fastmcp.server.transforms import ResourcesAsTools
|
||||||
|
from fastmcp.server.transforms.search import BM25SearchTransform, RegexSearchTransform
|
||||||
|
|
||||||
from personal_mcp.catalog.server import (
|
from personal_mcp.catalog.server import (
|
||||||
build_skill_detail_payload,
|
build_skill_detail_payload,
|
||||||
@@ -20,6 +22,8 @@ from personal_mcp.skills.document_loader import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
DOCS_ROOT = os.getenv("PERSONAL_MCP_DOCS_ROOT", "../../docs")
|
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(
|
REGISTRY: DocsRegistry = load_docs_registry(
|
||||||
package_anchor="personal_mcp",
|
package_anchor="personal_mcp",
|
||||||
docs_root=DOCS_ROOT,
|
docs_root=DOCS_ROOT,
|
||||||
@@ -28,6 +32,44 @@ REGISTRY: DocsRegistry = load_docs_registry(
|
|||||||
mcp = FastMCP("personal-mcp", on_duplicate="error")
|
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]:
|
def _ro_annotations() -> dict[str, bool]:
|
||||||
return {
|
return {
|
||||||
"readOnlyHint": True,
|
"readOnlyHint": True,
|
||||||
@@ -141,3 +183,6 @@ def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
|
|||||||
"found": True,
|
"found": True,
|
||||||
"document": read_skill_document(REGISTRY, skill_id),
|
"document": read_skill_document(REGISTRY, skill_id),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_install_tool_fallback_transforms()
|
||||||
|
|||||||
+4
-2
@@ -48,9 +48,11 @@ nav = [
|
|||||||
{ "Home" = "index.md" },
|
{ "Home" = "index.md" },
|
||||||
{ "Guide" = [
|
{ "Guide" = [
|
||||||
{ "Arch" = "architecture.md" },
|
{ "Arch" = "architecture.md" },
|
||||||
|
{ "Content" = "content.md" },
|
||||||
|
{ "Frontmatter" = "frontmatter.md" },
|
||||||
|
{ "URIs" = "uris.md" },
|
||||||
{ "MCP" = "mcp_layout.md" },
|
{ "MCP" = "mcp_layout.md" },
|
||||||
{ "Copilot" = "copilot.md" },
|
{ "Copilot" = "copilot.md" },
|
||||||
{ "Contracts 1-5" = "mcp_contract_steps_1_5.md" },
|
|
||||||
{ "Usage" = "usage.md" },
|
{ "Usage" = "usage.md" },
|
||||||
{ "Future Work" = "future_work.md" },
|
{ "Future Work" = "future_work.md" },
|
||||||
{ "New Skill" = "new_skill.md" },
|
{ "New Skill" = "new_skill.md" },
|
||||||
@@ -92,7 +94,7 @@ nav = [
|
|||||||
{ "Arch" = "skills/nicegui/references/architecture.md" },
|
{ "Arch" = "skills/nicegui/references/architecture.md" },
|
||||||
{ "Sources" = "skills/nicegui/references/source-documentation.md" },
|
{ "Sources" = "skills/nicegui/references/source-documentation.md" },
|
||||||
] },
|
] },
|
||||||
{ "NiceGUI UI" = [
|
{ "NiceGUI Fine-Tuning" = [
|
||||||
{ "Overview" = "skills/nicegui-ui-customization/SKILL.md" },
|
{ "Overview" = "skills/nicegui-ui-customization/SKILL.md" },
|
||||||
{ "Style" = "skills/nicegui-ui-customization/references/architecture-and-styling.md" },
|
{ "Style" = "skills/nicegui-ui-customization/references/architecture-and-styling.md" },
|
||||||
{ "Flows" = "skills/nicegui-ui-customization/references/interaction-patterns.md" },
|
{ "Flows" = "skills/nicegui-ui-customization/references/interaction-patterns.md" },
|
||||||
|
|||||||
Reference in New Issue
Block a user