Compare commits
2 Commits
59c638c634
...
36abea5940
| Author | SHA1 | Date | |
|---|---|---|---|
| 36abea5940 | |||
| 07475f972f |
@@ -21,7 +21,7 @@ This architecture keeps authored content human-friendly while preserving machine
|
|||||||
The architecture is designed to satisfy three long-term requirements:
|
The architecture is designed to satisfy three long-term requirements:
|
||||||
|
|
||||||
1. Methodology must be editable as markdown by humans.
|
1. Methodology must be editable as markdown by humans.
|
||||||
2. Agents must consume stable, discoverable resource contracts.
|
2. Agents must consume stable, discoverable resource contracts, with a minimal read-only catalog tool fallback for constrained clients.
|
||||||
3. Public documentation must be pre-built static output served from the application runtime without a separate docs service.
|
3. Public documentation must be pre-built static output served from the application runtime without a separate docs service.
|
||||||
|
|
||||||
## System Model
|
## System Model
|
||||||
@@ -36,7 +36,7 @@ The document resource returns canonical Markdown, while clients can perform any
|
|||||||
|
|
||||||
### Catalog Module
|
### Catalog Module
|
||||||
|
|
||||||
The catalog is the canonical discovery layer and publishes normalized records for all modules.
|
The catalog is the canonical discovery layer and publishes normalized records for all modules. It may also expose a minimal set of read-only discovery tools that resolve back to the same canonical markdown content when a client chat surface does not expose MCP resource attachment.
|
||||||
|
|
||||||
Typical catalog resources:
|
Typical catalog resources:
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ Markdown remains easy to review, while contracts remain stable for clients.
|
|||||||
|
|
||||||
### Client Independence
|
### Client Independence
|
||||||
|
|
||||||
Clients can use Ask, Edit, or Agent modes without requiring server-owned prompt orchestration.
|
Clients can use Ask, Edit, or Agent modes without requiring server-owned prompt orchestration. However, MCP affordances are still chat-surface-dependent: some clients or sessions expose resource attachment directly, while others make tool invocation the more reliable retrieval path.
|
||||||
|
|
||||||
## Authoring and Publishing Lifecycle
|
## Authoring and Publishing Lifecycle
|
||||||
|
|
||||||
@@ -148,9 +148,13 @@ In-scope:
|
|||||||
Out-of-scope:
|
Out-of-scope:
|
||||||
|
|
||||||
1. Prompt-first orchestration as the primary interface
|
1. Prompt-first orchestration as the primary interface
|
||||||
2. Large tool inventories duplicating static guidance
|
2. Large tool inventories duplicating static guidance across skill modules
|
||||||
3. Separate dynamic docs service at runtime
|
3. Separate dynamic docs service at runtime
|
||||||
|
|
||||||
|
Allowed exception:
|
||||||
|
|
||||||
|
1. A small catalog-level tool layer is acceptable when it improves client interoperability without creating a second source of truth for skill content.
|
||||||
|
|
||||||
## Example Content Inputs
|
## Example Content Inputs
|
||||||
|
|
||||||
Existing markdown reference sets are valid examples of authored source material for this architecture:
|
Existing markdown reference sets are valid examples of authored source material for this architecture:
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ Example mapping model:
|
|||||||
|
|
||||||
Catalog resources provide discovery metadata and stable identifiers.
|
Catalog resources provide discovery metadata and stable identifiers.
|
||||||
|
|
||||||
|
When clients cannot attach MCP resources directly, catalog-level tools may retrieve the same underlying skill documents indirectly. This does not create a second content source; it is only an alternate access path to the same markdown-backed contract.
|
||||||
|
|
||||||
## Why This Pattern
|
## Why This Pattern
|
||||||
|
|
||||||
### Operational Simplicity
|
### Operational Simplicity
|
||||||
|
|||||||
+10
-4
@@ -14,7 +14,9 @@ Use this checklist after generating a new skill under `docs/skills/<slug>/`.
|
|||||||
Create `src/personal_mcp/skills/<python_namespace>/` with `__init__.py`, `server.py`, and `metadata.yaml`.
|
Create `src/personal_mcp/skills/<python_namespace>/` with `__init__.py`, `server.py`, and `metadata.yaml`.
|
||||||
|
|
||||||
4. Expose the document resource in `server.py`.
|
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>)`.
|
Follow the existing pattern: create a `FastMCP` instance, register `resource://skills/<skill-id>/document`, and return the shared loader result.
|
||||||
|
For the default layout, load `docs/skills/<slug>/SKILL.md`.
|
||||||
|
For special cases, set `document_path` in `metadata.yaml` to a repo-relative Markdown file and load from metadata instead of hardcoding a path in the server.
|
||||||
|
|
||||||
5. Register the catalog metadata.
|
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`.
|
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`.
|
||||||
@@ -23,7 +25,7 @@ Use this checklist after generating a new skill under `docs/skills/<slug>/`.
|
|||||||
Import the new server in `src/personal_mcp/mcp.py` and add an `mcp.mount(...)` call with the Python namespace.
|
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.
|
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.
|
The document loader reads canonical Markdown from `docs/skills/<slug>/SKILL.md` by default, or from `metadata.yaml`'s optional `document_path` override when present. The catalog discovers metadata from `src/personal_mcp/skills/*/metadata.yaml` automatically.
|
||||||
|
|
||||||
8. Rebuild and smoke-test.
|
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.
|
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.
|
||||||
@@ -39,8 +41,9 @@ To keep behavior consistent across MCP clients and Copilot session types, follow
|
|||||||
### Do
|
### Do
|
||||||
|
|
||||||
1. Add or update `metadata.yaml` fields (`id`, `description`, `tags`, `capabilities`) so catalog discovery quality stays high.
|
1. Add or update `metadata.yaml` fields (`id`, `description`, `tags`, `capabilities`) so catalog discovery quality stays high.
|
||||||
2. Use catalog resources as the primary discovery surface.
|
2. Use `document_path` when a skill should expose a Markdown file outside `docs/skills/<slug>/SKILL.md`.
|
||||||
3. Add thin, read-only catalog tools only when client behavior needs a fallback path.
|
3. Use catalog resources as the primary discovery surface.
|
||||||
|
4. Add thin, read-only catalog tools only when client behavior needs a fallback path.
|
||||||
|
|
||||||
### Don't
|
### Don't
|
||||||
|
|
||||||
@@ -92,11 +95,14 @@ description: <One sentence describing what the skill provides.>
|
|||||||
tags:
|
tags:
|
||||||
- <tag-one>
|
- <tag-one>
|
||||||
- <tag-two>
|
- <tag-two>
|
||||||
|
document_path: <optional repo-relative path to a markdown file>
|
||||||
capabilities:
|
capabilities:
|
||||||
- resource://skills/<skill-id>/document
|
- resource://skills/<skill-id>/document
|
||||||
depends_on: []
|
depends_on: []
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Omit `document_path` when the canonical document is `docs/skills/<slug>/SKILL.md`.
|
||||||
|
|
||||||
## Root Mount Template
|
## Root Mount Template
|
||||||
|
|
||||||
Add an import in `src/personal_mcp/mcp.py`:
|
Add an import in `src/personal_mcp/mcp.py`:
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ Always start a new documentation project with `uv run zensical new`.
|
|||||||
- This command provides the baseline scaffolding you need for structure, configuration, and theme setup.
|
- This command provides the baseline scaffolding you need for structure, configuration, and theme setup.
|
||||||
- Do not hand-build a new project skeleton when this command is available.
|
- Do not hand-build a new project skeleton when this command is available.
|
||||||
|
|
||||||
|
## Related Skill Discovery
|
||||||
|
|
||||||
|
When the task is not just writing docs but creating or wiring a new MCP skill in this repository, use catalog discovery to load the bootstrap skill before drafting implementation steps.
|
||||||
|
|
||||||
|
1. Search the catalog with terms such as `new skill`, `skill bootstrap`, or `scaffold skill`.
|
||||||
|
2. Fetch the `new-skill` document through the catalog tool path.
|
||||||
|
3. Use that skill for runtime package, metadata, and mount wiring, then return here for documentation architecture and Zensical-specific authoring guidance.
|
||||||
|
|
||||||
|
This keeps implementation guidance and documentation guidance separated while still making both discoverable from one request.
|
||||||
|
|
||||||
## Inputs To Collect
|
## Inputs To Collect
|
||||||
|
|
||||||
Collect these before writing. If missing, make explicit assumptions.
|
Collect these before writing. If missing, make explicit assumptions.
|
||||||
|
|||||||
@@ -147,6 +147,50 @@ 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.
|
||||||
|
|
||||||
|
## Copilot Instruction Pattern
|
||||||
|
|
||||||
|
If you want Copilot to use `personal-mcp` skill content more reliably, the instruction file should describe three things clearly:
|
||||||
|
|
||||||
|
1. when MCP-backed skill guidance is relevant
|
||||||
|
2. which retrieval path Copilot should prefer first
|
||||||
|
3. how much skill context it should load before answering
|
||||||
|
|
||||||
|
That matters because instructions can strongly steer discovery behavior, but they do not force VS Code to auto-attach MCP resources. A good instruction tells Copilot to prefer the canonical MCP content path while remaining accurate about the fallback path.
|
||||||
|
|
||||||
|
In this repository, the right policy is:
|
||||||
|
|
||||||
|
1. start from catalog discovery
|
||||||
|
2. prefer MCP resources when the current chat surface exposes resource attachment
|
||||||
|
3. fall back to catalog tools when resource attachment is unavailable
|
||||||
|
4. keep loaded skill context bounded
|
||||||
|
|
||||||
|
Suggested instruction text:
|
||||||
|
|
||||||
|
```md
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Preferred resource order:
|
||||||
|
|
||||||
|
1. `resource://catalog/skills_index` or `resource://catalog/patterns`
|
||||||
|
2. `resource://skills/<skill-id>/document`
|
||||||
|
|
||||||
|
Preferred tool fallback order:
|
||||||
|
|
||||||
|
1. `search_patterns`
|
||||||
|
2. `get_pattern_by_id`
|
||||||
|
3. `get_skill_document_by_id`
|
||||||
|
|
||||||
|
If confidence is low after discovery, ask one clarifying question before loading more context.
|
||||||
|
```
|
||||||
|
|
||||||
|
This is intentionally guidance, not a guarantee. It gives Copilot a strong policy for when to use resources and when to fall back to discovery tools, while preserving the resource-first architecture.
|
||||||
|
|
||||||
## Failure Modes and Recovery
|
## Failure Modes and Recovery
|
||||||
|
|
||||||
Common failure modes:
|
Common failure modes:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any
|
|||||||
import yaml
|
import yaml
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
from personal_mcp.skills.document_loader import load_skill_document
|
from personal_mcp.skills.document_loader import load_skill_document_from_metadata
|
||||||
|
|
||||||
catalog_server = FastMCP("catalog")
|
catalog_server = FastMCP("catalog")
|
||||||
|
|
||||||
@@ -59,6 +59,10 @@ def _matches_query(pattern: dict[str, Any], query: str) -> bool:
|
|||||||
if not lowered:
|
if not lowered:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
query_terms = [term for term in lowered.replace("-", " ").split() if term]
|
||||||
|
if not query_terms:
|
||||||
|
return True
|
||||||
|
|
||||||
haystack = " ".join(
|
haystack = " ".join(
|
||||||
[
|
[
|
||||||
str(pattern.get("id", "")),
|
str(pattern.get("id", "")),
|
||||||
@@ -68,7 +72,7 @@ def _matches_query(pattern: dict[str, Any], query: str) -> bool:
|
|||||||
" ".join(str(tag) for tag in pattern.get("tags", [])),
|
" ".join(str(tag) for tag in pattern.get("tags", [])),
|
||||||
]
|
]
|
||||||
).lower()
|
).lower()
|
||||||
return lowered in haystack
|
return all(term in haystack for term in query_terms)
|
||||||
|
|
||||||
|
|
||||||
def _matches_tags(pattern: dict[str, Any], tags: list[str] | None) -> bool:
|
def _matches_tags(pattern: dict[str, Any], tags: list[str] | None) -> bool:
|
||||||
@@ -83,34 +87,6 @@ def _matches_tags(pattern: dict[str, Any], tags: list[str] | None) -> bool:
|
|||||||
return all(tag in pattern_tags for tag in requested)
|
return all(tag in pattern_tags for tag in requested)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_skill_slug(*, skill_id: str, namespace: str, metadata: dict[str, Any]) -> str:
|
|
||||||
candidates: list[str] = []
|
|
||||||
slug = metadata.get("slug")
|
|
||||||
if isinstance(slug, str) and slug.strip():
|
|
||||||
candidates.append(slug.strip())
|
|
||||||
|
|
||||||
candidates.extend(
|
|
||||||
[
|
|
||||||
skill_id,
|
|
||||||
namespace.replace("_", "-"),
|
|
||||||
namespace,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
seen: set[str] = set()
|
|
||||||
for candidate in candidates:
|
|
||||||
if candidate in seen:
|
|
||||||
continue
|
|
||||||
seen.add(candidate)
|
|
||||||
try:
|
|
||||||
load_skill_document(skill_id=skill_id, skill_slug=candidate)
|
|
||||||
return candidate
|
|
||||||
except FileNotFoundError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return skill_id
|
|
||||||
|
|
||||||
|
|
||||||
@catalog_server.resource("resource://catalog/skills_index")
|
@catalog_server.resource("resource://catalog/skills_index")
|
||||||
def skills_index() -> dict[str, Any]:
|
def skills_index() -> dict[str, Any]:
|
||||||
"""Return a compact discovery index for all available pattern modules."""
|
"""Return a compact discovery index for all available pattern modules."""
|
||||||
@@ -184,14 +160,13 @@ def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
|
|||||||
if pattern_id != skill_id:
|
if pattern_id != skill_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
skill_slug = _resolve_skill_slug(
|
return {
|
||||||
|
"found": True,
|
||||||
|
"document": load_skill_document_from_metadata(
|
||||||
skill_id=skill_id,
|
skill_id=skill_id,
|
||||||
namespace=namespace,
|
namespace=namespace,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
)
|
),
|
||||||
return {
|
|
||||||
"found": True,
|
|
||||||
"document": load_skill_document(skill_id=skill_id, skill_slug=skill_slug),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {"found": False, "id": skill_id}
|
return {"found": False, "id": skill_id}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from personal_mcp.skills.pytest_scaffolding.server import pytest_scaffolding_ser
|
|||||||
from personal_mcp.skills.python_logging_dictconfig.server import (
|
from personal_mcp.skills.python_logging_dictconfig.server import (
|
||||||
python_logging_dictconfig_server,
|
python_logging_dictconfig_server,
|
||||||
)
|
)
|
||||||
|
from personal_mcp.skills.zensical_docs.server import zensical_docs_server
|
||||||
|
|
||||||
mcp = FastMCP("personal-mcp")
|
mcp = FastMCP("personal-mcp")
|
||||||
|
|
||||||
@@ -28,3 +29,4 @@ 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")
|
||||||
mcp.mount(python_logging_dictconfig_server, namespace="python_logging_dictconfig")
|
mcp.mount(python_logging_dictconfig_server, namespace="python_logging_dictconfig")
|
||||||
mcp.mount(fastapi_uv_docker_server, namespace="fastapi_uv_docker")
|
mcp.mount(fastapi_uv_docker_server, namespace="fastapi_uv_docker")
|
||||||
|
mcp.mount(zensical_docs_server, namespace="zensical_docs")
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def _repo_root() -> Path:
|
def _repo_root() -> Path:
|
||||||
return Path(__file__).resolve().parents[3]
|
return Path(__file__).resolve().parents[3]
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_skill_document_path(
|
||||||
|
*, skill_id: str, namespace: str, metadata: dict[str, Any]
|
||||||
|
) -> Path:
|
||||||
|
"""Resolve the canonical Markdown document path for a skill."""
|
||||||
|
document_path = metadata.get("document_path")
|
||||||
|
if isinstance(document_path, str) and document_path.strip():
|
||||||
|
return _repo_root() / document_path.strip()
|
||||||
|
|
||||||
|
candidates: list[str] = []
|
||||||
|
slug = metadata.get("slug")
|
||||||
|
if isinstance(slug, str) and slug.strip():
|
||||||
|
candidates.append(slug.strip())
|
||||||
|
|
||||||
|
candidates.extend(
|
||||||
|
[
|
||||||
|
skill_id,
|
||||||
|
namespace.replace("_", "-"),
|
||||||
|
namespace,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
seen: set[str] = set()
|
||||||
|
for candidate in candidates:
|
||||||
|
if candidate in seen:
|
||||||
|
continue
|
||||||
|
seen.add(candidate)
|
||||||
|
|
||||||
|
candidate_path = _repo_root() / "docs" / "skills" / candidate / "SKILL.md"
|
||||||
|
if candidate_path.exists():
|
||||||
|
return candidate_path
|
||||||
|
|
||||||
|
return _repo_root() / "docs" / "skills" / skill_id / "SKILL.md"
|
||||||
|
|
||||||
|
|
||||||
def load_markdown_document(*, skill_id: str, document_path: Path) -> dict[str, str]:
|
def load_markdown_document(*, skill_id: str, document_path: Path) -> dict[str, str]:
|
||||||
"""Load an arbitrary Markdown document and expose it as a skill resource."""
|
"""Load an arbitrary Markdown document and expose it as a skill resource."""
|
||||||
if not document_path.exists():
|
if not document_path.exists():
|
||||||
@@ -25,3 +60,15 @@ def load_skill_document(*, skill_id: str, skill_slug: str) -> dict[str, str]:
|
|||||||
"""Load the canonical skill markdown document for an MCP skill."""
|
"""Load the canonical skill markdown document for an MCP skill."""
|
||||||
document_path = _repo_root() / "docs" / "skills" / skill_slug / "SKILL.md"
|
document_path = _repo_root() / "docs" / "skills" / skill_slug / "SKILL.md"
|
||||||
return load_markdown_document(skill_id=skill_id, document_path=document_path)
|
return load_markdown_document(skill_id=skill_id, document_path=document_path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_skill_document_from_metadata(
|
||||||
|
*, skill_id: str, namespace: str, metadata: dict[str, Any]
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Load a skill document using metadata overrides when present."""
|
||||||
|
document_path = resolve_skill_document_path(
|
||||||
|
skill_id=skill_id,
|
||||||
|
namespace=namespace,
|
||||||
|
metadata=metadata,
|
||||||
|
)
|
||||||
|
return load_markdown_document(skill_id=skill_id, document_path=document_path)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ tags:
|
|||||||
- scaffolding
|
- scaffolding
|
||||||
- skills
|
- skills
|
||||||
- mcp
|
- mcp
|
||||||
|
document_path: docs/new_skill.md
|
||||||
capabilities:
|
capabilities:
|
||||||
- resource://skills/new-skill/document
|
- resource://skills/new-skill/document
|
||||||
depends_on: []
|
depends_on: []
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
from personal_mcp.skills.document_loader import load_markdown_document
|
from personal_mcp.skills.document_loader import load_skill_document_from_metadata
|
||||||
|
|
||||||
new_skill_server = FastMCP("new-skill")
|
new_skill_server = FastMCP("new-skill")
|
||||||
|
_METADATA_PATH = Path(__file__).with_name("metadata.yaml")
|
||||||
|
_METADATA = yaml.safe_load(_METADATA_PATH.read_text(encoding="utf-8")) or {}
|
||||||
|
|
||||||
|
|
||||||
@new_skill_server.resource("resource://skills/new-skill/document")
|
@new_skill_server.resource("resource://skills/new-skill/document")
|
||||||
def skill_document() -> dict[str, str]:
|
def skill_document() -> dict[str, str]:
|
||||||
"""Return the bootstrap guide used to scaffold new skills."""
|
"""Return the bootstrap guide used to scaffold new skills."""
|
||||||
document_path = Path(__file__).resolve().parents[4] / "docs" / "new_skill.md"
|
return load_skill_document_from_metadata(
|
||||||
return load_markdown_document(
|
|
||||||
skill_id="new-skill",
|
skill_id="new-skill",
|
||||||
document_path=document_path,
|
namespace="new_skill",
|
||||||
|
metadata=_METADATA,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Zensical documentation authoring skill server."""
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
id: zensical-docs
|
||||||
|
name: Zensical Documentation Authoring
|
||||||
|
version: 1.0.0
|
||||||
|
description: Plan, write, and improve high-quality documentation with Zensical.
|
||||||
|
tags:
|
||||||
|
- zensical
|
||||||
|
- docs
|
||||||
|
- documentation
|
||||||
|
- information-architecture
|
||||||
|
- skills
|
||||||
|
- bootstrap
|
||||||
|
- discovery
|
||||||
|
- authoring
|
||||||
|
capabilities:
|
||||||
|
- resource://skills/zensical-docs/document
|
||||||
|
depends_on: []
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from fastmcp import FastMCP
|
||||||
|
|
||||||
|
from personal_mcp.skills.document_loader import load_skill_document
|
||||||
|
|
||||||
|
zensical_docs_server = FastMCP("zensical-docs")
|
||||||
|
|
||||||
|
|
||||||
|
@zensical_docs_server.resource("resource://skills/zensical-docs/document")
|
||||||
|
def skill_document() -> dict[str, str]:
|
||||||
|
"""Return the canonical Markdown document for this skill."""
|
||||||
|
return load_skill_document(
|
||||||
|
skill_id="zensical-docs",
|
||||||
|
skill_slug="zensical-docs",
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user