new skill meta
This commit is contained in:
@@ -17,7 +17,6 @@ COPY src ./src
|
|||||||
RUN uv sync --frozen --no-dev
|
RUN uv sync --frozen --no-dev
|
||||||
|
|
||||||
COPY docs ./docs
|
COPY docs ./docs
|
||||||
COPY skills ./skills
|
|
||||||
COPY zensical.toml ./
|
COPY zensical.toml ./
|
||||||
|
|
||||||
RUN uv run zensical build
|
RUN uv run zensical build
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# Hooking Up a New Skill
|
||||||
|
|
||||||
|
Use this checklist after generating a new skill under `docs/skills/<slug>/`.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
1. Create the authored docs content.
|
||||||
|
Add `docs/skills/<slug>/SKILL.md` and any companion files under `docs/skills/<slug>/references/`.
|
||||||
|
|
||||||
|
2. Choose the three names up front.
|
||||||
|
Use a docs slug like `fastapi-uv-docker`, a resource id like `fastapi-uv-docker`, and a Python package name like `fastapi_uv_docker`.
|
||||||
|
|
||||||
|
3. Add the runtime package.
|
||||||
|
Create `src/personal_mcp/skills/<python_namespace>/` with `__init__.py`, `server.py`, and `metadata.yaml`.
|
||||||
|
|
||||||
|
4. Expose the document resource in `server.py`.
|
||||||
|
Follow the existing pattern: create a `FastMCP` instance, register `resource://skills/<skill-id>/document`, and return `load_skill_document(skill_id=<skill-id>, skill_slug=<slug>)`.
|
||||||
|
|
||||||
|
5. Register the catalog metadata.
|
||||||
|
In `metadata.yaml`, add the skill `id`, `name`, `version`, `description`, `tags`, `capabilities`, and `depends_on`. The `capabilities` list should include `resource://skills/<skill-id>/document`.
|
||||||
|
|
||||||
|
6. Mount the skill in the root server.
|
||||||
|
Import the new server in `src/personal_mcp/mcp.py` and add an `mcp.mount(...)` call with the Python namespace.
|
||||||
|
|
||||||
|
7. Let the loader and catalog do the rest.
|
||||||
|
The document loader reads canonical Markdown from `docs/skills/<slug>/SKILL.md`, and the catalog discovers metadata from `src/personal_mcp/skills/*/metadata.yaml` automatically.
|
||||||
|
|
||||||
|
8. Rebuild and smoke-test.
|
||||||
|
Run `uv run zensical build` to publish the docs site, then run a quick Python check or start the app to confirm the new resource loads.
|
||||||
|
|
||||||
|
## Minimal Shape
|
||||||
|
|
||||||
|
- Docs content: `docs/skills/<slug>/SKILL.md`
|
||||||
|
- Optional references: `docs/skills/<slug>/references/*.md`
|
||||||
|
- Runtime package: `src/personal_mcp/skills/<python_namespace>/`
|
||||||
|
- Resource URI: `resource://skills/<skill-id>/document`
|
||||||
|
|
||||||
|
## Quick Validation
|
||||||
|
|
||||||
|
1. Confirm the Markdown document resolves through the loader.
|
||||||
|
`uv run python -c "from personal_mcp.skills.document_loader import load_skill_document; print(load_skill_document(skill_id='<skill-id>', skill_slug='<slug>')['source_path'])"`
|
||||||
|
|
||||||
|
2. Confirm the docs build still works.
|
||||||
|
`uv run zensical build`
|
||||||
|
|
||||||
|
## server.py Template
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
from personal_mcp.skills.document_loader import load_skill_document
|
||||||
|
|
||||||
|
<python_namespace>_server = FastMCP("<skill-id>")
|
||||||
|
|
||||||
|
|
||||||
|
@<python_namespace>_server.resource("resource://skills/<skill-id>/document")
|
||||||
|
def skill_document() -> dict[str, str]:
|
||||||
|
"""Return the canonical Markdown document for this skill."""
|
||||||
|
return load_skill_document(
|
||||||
|
skill_id="<skill-id>",
|
||||||
|
skill_slug="<slug>",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## metadata.yaml Template
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
id: <skill-id>
|
||||||
|
name: <Human Readable Name>
|
||||||
|
version: 1.0.0
|
||||||
|
description: <One sentence describing what the skill provides.>
|
||||||
|
tags:
|
||||||
|
- <tag-one>
|
||||||
|
- <tag-two>
|
||||||
|
capabilities:
|
||||||
|
- resource://skills/<skill-id>/document
|
||||||
|
depends_on: []
|
||||||
|
```
|
||||||
|
|
||||||
|
## Root Mount Template
|
||||||
|
|
||||||
|
Add an import in `src/personal_mcp/mcp.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from personal_mcp.skills.<python_namespace>.server import <python_namespace>_server
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a mount call:
|
||||||
|
|
||||||
|
```python
|
||||||
|
mcp.mount(<python_namespace>_server, namespace="<python_namespace>")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Scaffold
|
||||||
|
|
||||||
|
For a new skill called `sqlmodel-patterns`:
|
||||||
|
|
||||||
|
1. Docs content lives in `docs/skills/sqlmodel-patterns/SKILL.md`.
|
||||||
|
2. The Python package lives in `src/personal_mcp/skills/sqlmodel_patterns/`.
|
||||||
|
3. The resource id is `sqlmodel-patterns`.
|
||||||
|
|
||||||
|
Example `server.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
from personal_mcp.skills.document_loader import load_skill_document
|
||||||
|
|
||||||
|
sqlmodel_patterns_server = FastMCP("sqlmodel-patterns")
|
||||||
|
|
||||||
|
|
||||||
|
@sqlmodel_patterns_server.resource("resource://skills/sqlmodel-patterns/document")
|
||||||
|
def skill_document() -> dict[str, str]:
|
||||||
|
"""Return the canonical Markdown document for this skill."""
|
||||||
|
return load_skill_document(
|
||||||
|
skill_id="sqlmodel-patterns",
|
||||||
|
skill_slug="sqlmodel-patterns",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `metadata.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
id: sqlmodel-patterns
|
||||||
|
name: SQLModel Patterns
|
||||||
|
version: 1.0.0
|
||||||
|
description: Provide reusable patterns for building apps with SQLModel.
|
||||||
|
tags:
|
||||||
|
- sqlmodel
|
||||||
|
- python
|
||||||
|
- patterns
|
||||||
|
capabilities:
|
||||||
|
- resource://skills/sqlmodel-patterns/document
|
||||||
|
depends_on: []
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `mcp.py` additions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from personal_mcp.skills.sqlmodel_patterns.server import sqlmodel_patterns_server
|
||||||
|
|
||||||
|
mcp.mount(sqlmodel_patterns_server, namespace="sqlmodel_patterns")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bootstrap Sequence
|
||||||
|
|
||||||
|
1. Create `docs/skills/<slug>/SKILL.md`.
|
||||||
|
2. Copy the `server.py` template into `src/personal_mcp/skills/<python_namespace>/server.py`.
|
||||||
|
3. Copy the `metadata.yaml` template into `src/personal_mcp/skills/<python_namespace>/metadata.yaml`.
|
||||||
|
4. Add `__init__.py` in the new package directory.
|
||||||
|
5. Import and mount the server in `src/personal_mcp/mcp.py`.
|
||||||
|
6. Run the validation commands above.
|
||||||
@@ -5,6 +5,7 @@ from personal_mcp.skills.fastapi_async_sqlalchemy_modernization.server import (
|
|||||||
fastapi_async_sqlalchemy_modernization_server,
|
fastapi_async_sqlalchemy_modernization_server,
|
||||||
)
|
)
|
||||||
from personal_mcp.skills.fastapi_uv_docker.server import fastapi_uv_docker_server
|
from personal_mcp.skills.fastapi_uv_docker.server import fastapi_uv_docker_server
|
||||||
|
from personal_mcp.skills.new_skill.server import new_skill_server
|
||||||
from personal_mcp.skills.nicegui.server import nicegui_server
|
from personal_mcp.skills.nicegui.server import nicegui_server
|
||||||
from personal_mcp.skills.nicegui_ui_customization.server import (
|
from personal_mcp.skills.nicegui_ui_customization.server import (
|
||||||
nicegui_ui_customization_server,
|
nicegui_ui_customization_server,
|
||||||
@@ -21,6 +22,7 @@ mcp.mount(
|
|||||||
fastapi_async_sqlalchemy_modernization_server,
|
fastapi_async_sqlalchemy_modernization_server,
|
||||||
namespace="fastapi_async_sqlalchemy_modernization",
|
namespace="fastapi_async_sqlalchemy_modernization",
|
||||||
)
|
)
|
||||||
|
mcp.mount(new_skill_server, namespace="new_skill")
|
||||||
mcp.mount(nicegui_server, namespace="nicegui")
|
mcp.mount(nicegui_server, namespace="nicegui")
|
||||||
mcp.mount(nicegui_ui_customization_server, namespace="nicegui_ui_customization")
|
mcp.mount(nicegui_ui_customization_server, namespace="nicegui_ui_customization")
|
||||||
mcp.mount(pytest_scaffolding_server, namespace="pytest_scaffolding")
|
mcp.mount(pytest_scaffolding_server, namespace="pytest_scaffolding")
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ def _repo_root() -> Path:
|
|||||||
return Path(__file__).resolve().parents[3]
|
return Path(__file__).resolve().parents[3]
|
||||||
|
|
||||||
|
|
||||||
def load_skill_document(*, skill_id: str, skill_slug: str) -> dict[str, str]:
|
def load_markdown_document(*, skill_id: str, document_path: Path) -> dict[str, str]:
|
||||||
"""Load the canonical skill markdown document for an MCP skill."""
|
"""Load an arbitrary Markdown document and expose it as a skill resource."""
|
||||||
document_path = _repo_root() / "docs" / "skills" / skill_slug / "SKILL.md"
|
|
||||||
if not document_path.exists():
|
if not document_path.exists():
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
f"Missing skill document for '{skill_id}': {document_path}"
|
f"Missing skill document for '{skill_id}': {document_path}"
|
||||||
@@ -20,3 +19,9 @@ def load_skill_document(*, skill_id: str, skill_slug: str) -> dict[str, str]:
|
|||||||
"source_path": str(document_path),
|
"source_path": str(document_path),
|
||||||
"content": document_path.read_text(encoding="utf-8"),
|
"content": document_path.read_text(encoding="utf-8"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_skill_document(*, skill_id: str, skill_slug: str) -> dict[str, str]:
|
||||||
|
"""Load the canonical skill markdown document for an MCP skill."""
|
||||||
|
document_path = _repo_root() / "docs" / "skills" / skill_slug / "SKILL.md"
|
||||||
|
return load_markdown_document(skill_id=skill_id, document_path=document_path)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Pseudo-skill exposing the new skill bootstrap guide."""
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
id: new-skill
|
||||||
|
name: New Skill Bootstrap
|
||||||
|
version: 1.0.0
|
||||||
|
description: Provide the bootstrap checklist and templates for creating new MCP skills.
|
||||||
|
tags:
|
||||||
|
- bootstrap
|
||||||
|
- scaffolding
|
||||||
|
- skills
|
||||||
|
- mcp
|
||||||
|
capabilities:
|
||||||
|
- resource://skills/new-skill/document
|
||||||
|
depends_on: []
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
from personal_mcp.skills.document_loader import load_markdown_document
|
||||||
|
|
||||||
|
new_skill_server = FastMCP("new-skill")
|
||||||
|
|
||||||
|
|
||||||
|
@new_skill_server.resource("resource://skills/new-skill/document")
|
||||||
|
def skill_document() -> dict[str, str]:
|
||||||
|
"""Return the bootstrap guide used to scaffold new skills."""
|
||||||
|
document_path = Path(__file__).resolve().parents[4] / "docs" / "new_skill.md"
|
||||||
|
return load_markdown_document(
|
||||||
|
skill_id="new-skill",
|
||||||
|
document_path=document_path,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user