zensical docs skill
This commit is contained in:
@@ -4,7 +4,7 @@ from typing import Any
|
||||
import yaml
|
||||
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")
|
||||
|
||||
@@ -59,6 +59,10 @@ def _matches_query(pattern: dict[str, Any], query: str) -> bool:
|
||||
if not lowered:
|
||||
return True
|
||||
|
||||
query_terms = [term for term in lowered.replace("-", " ").split() if term]
|
||||
if not query_terms:
|
||||
return True
|
||||
|
||||
haystack = " ".join(
|
||||
[
|
||||
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", [])),
|
||||
]
|
||||
).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:
|
||||
@@ -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)
|
||||
|
||||
|
||||
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")
|
||||
def skills_index() -> dict[str, Any]:
|
||||
"""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:
|
||||
continue
|
||||
|
||||
skill_slug = _resolve_skill_slug(
|
||||
skill_id=skill_id,
|
||||
namespace=namespace,
|
||||
metadata=metadata,
|
||||
)
|
||||
return {
|
||||
"found": True,
|
||||
"document": load_skill_document(skill_id=skill_id, skill_slug=skill_slug),
|
||||
"document": load_skill_document_from_metadata(
|
||||
skill_id=skill_id,
|
||||
namespace=namespace,
|
||||
metadata=metadata,
|
||||
),
|
||||
}
|
||||
|
||||
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 (
|
||||
python_logging_dictconfig_server,
|
||||
)
|
||||
from personal_mcp.skills.zensical_docs.server import zensical_docs_server
|
||||
|
||||
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(python_logging_dictconfig_server, namespace="python_logging_dictconfig")
|
||||
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 typing import Any
|
||||
|
||||
|
||||
def _repo_root() -> Path:
|
||||
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]:
|
||||
"""Load an arbitrary Markdown document and expose it as a skill resource."""
|
||||
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."""
|
||||
document_path = _repo_root() / "docs" / "skills" / skill_slug / "SKILL.md"
|
||||
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
|
||||
- skills
|
||||
- mcp
|
||||
document_path: docs/new_skill.md
|
||||
capabilities:
|
||||
- resource://skills/new-skill/document
|
||||
depends_on: []
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
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")
|
||||
_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")
|
||||
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(
|
||||
return load_skill_document_from_metadata(
|
||||
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