WIP simplifying load/startup

This commit is contained in:
John Lancaster
2026-07-26 17:23:39 -05:00
parent 5e20f69cfe
commit 42ea105bee
30 changed files with 855 additions and 766 deletions
+172 -187
View File
@@ -20,22 +20,15 @@ from personal_mcp.catalog.server import get_pattern_by_id_payload
from personal_mcp.catalog.server import get_prompt_by_id_payload
from personal_mcp.catalog.server import search_patterns_payload
from personal_mcp.catalog.server import search_prompts_payload
from personal_mcp.registry.load import load_docs_registry
from personal_mcp.registry.load import get_docs_registry
from personal_mcp.registry.models.registry import DocsRegistry
from personal_mcp.registry.read import read_docs_markdown_path
from personal_mcp.registry.read import read_prompt_document
from personal_mcp.registry.read import read_skill_document
from personal_mcp.registry.read import read_skill_reference
DOCS_ROOT = os.getenv("PERSONAL_MCP_DOCS_ROOT", "../../docs")
TOOL_SEARCH_MODE = os.getenv("PERSONAL_MCP_TOOL_SEARCH", "none").strip().lower()
TOOL_SEARCH_MAX_RESULTS = os.getenv("PERSONAL_MCP_TOOL_SEARCH_MAX_RESULTS", "5")
REGISTRY: DocsRegistry = load_docs_registry(
package_anchor="personal_mcp",
docs_root=DOCS_ROOT,
)
mcp = FastMCP("personal-mcp", on_duplicate="error")
def _parse_positive_int(value: str, *, env_name: str) -> int:
@@ -48,7 +41,7 @@ def _parse_positive_int(value: str, *, env_name: str) -> int:
return parsed
def _install_tool_fallback_transforms() -> None:
def _install_tool_fallback_transforms(mcp: FastMCP) -> None:
# Expose list_resources/read_resource for tool-only clients.
mcp.add_transform(ResourcesAsTools(mcp))
@@ -95,9 +88,9 @@ def _make_prompt_handler(content: str):
return prompt_handler
def _register_prompt_objects() -> None:
for prompt_id in REGISTRY.prompts_in_load_order:
prompt = REGISTRY.prompts_by_id[prompt_id]
def _register_prompt_objects(mcp: FastMCP, registry: DocsRegistry) -> None:
for prompt_id in registry.prompts_in_load_order:
prompt = registry.prompts_by_id[prompt_id]
annotations: dict[str, Any] = {}
params: list[Parameter] = []
@@ -129,187 +122,179 @@ def _register_prompt_objects() -> None:
)
@mcp.resource(
"resource://catalog/skills_index",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def skills_index() -> dict[str, Any]:
return build_skills_index_payload(REGISTRY)
@mcp.resource(
"resource://catalog/skills_index{?q,tag,capability,cursor,limit}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def skills_index_query(
q: str | None = None,
tag: str | None = None,
capability: str | None = None,
cursor: str | None = None,
limit: int | None = None,
) -> dict[str, Any]:
return build_skills_index_payload(
REGISTRY,
query=q,
tag=tag,
capability=capability,
cursor=cursor,
limit=limit,
def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
@mcp.resource(
"resource://catalog/skills_index",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def skills_index() -> dict[str, Any]:
return build_skills_index_payload(registry)
@mcp.resource(
"resource://catalog/skills/{skill_id}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def skill_detail(skill_id: str) -> dict[str, Any]:
return build_skill_detail_payload(REGISTRY, skill_id)
@mcp.resource(
"resource://skills/{skill_id}/document",
mime_type="text/markdown",
tags={"skill-doc"},
annotations=_ro_annotations(),
)
def skill_document(skill_id: str) -> dict[str, str]:
return read_skill_document(REGISTRY, skill_id)
@mcp.resource(
"resource://skills/{skill_id}/references/{ref_id}",
mime_type="text/markdown",
tags={"reference"},
annotations=_ro_annotations(),
)
def skill_reference(skill_id: str, ref_id: str) -> dict[str, str]:
return read_skill_reference(REGISTRY, skill_id=skill_id, ref_id=ref_id)
@mcp.resource(
"resource://docs/{path*}",
mime_type="text/markdown",
tags={"docs"},
annotations=_ro_annotations(),
)
def docs_markdown(path: str) -> dict[str, str]:
return read_docs_markdown_path(REGISTRY, path)
@mcp.resource(
"resource://catalog/prompts_index",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def prompts_index() -> dict[str, Any]:
return build_prompts_index_payload(REGISTRY)
@mcp.resource(
"resource://catalog/prompts_index{?q,tag,cursor,limit}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def prompts_index_query(
q: str | None = None,
tag: str | None = None,
cursor: str | None = None,
limit: int | None = None,
) -> dict[str, Any]:
return build_prompts_index_payload(
REGISTRY,
query=q,
tag=tag,
cursor=cursor,
limit=limit,
@mcp.resource(
"resource://catalog/skills_index{?q,tag,capability,cursor,limit}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def skills_index_query(
q: str | None = None,
tag: str | None = None,
capability: str | None = None,
cursor: str | None = None,
limit: int | None = None,
) -> dict[str, Any]:
return build_skills_index_payload(
registry,
query=q,
tag=tag,
capability=capability,
cursor=cursor,
limit=limit,
)
@mcp.resource(
"resource://catalog/prompts/{prompt_id}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def prompt_detail(prompt_id: str) -> dict[str, Any]:
return build_prompt_detail_payload(REGISTRY, prompt_id)
@mcp.resource(
"resource://prompts/{prompt_id}/document",
mime_type="text/markdown",
tags={"prompt-doc"},
annotations=_ro_annotations(),
)
def prompt_document(prompt_id: str) -> dict[str, str]:
return read_prompt_document(REGISTRY, prompt_id)
@mcp.tool
def search_patterns(
query: str = "",
tags: list[str] | None = None,
skip: int = 0,
limit: int = 20,
) -> dict[str, Any]:
"""Search normalized pattern metadata with optional tags and pagination."""
return search_patterns_payload(
REGISTRY,
query=query,
tags=tags,
skip=skip,
limit=limit,
@mcp.resource(
"resource://catalog/skills/{skill_id}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def skill_detail(skill_id: str) -> dict[str, Any]:
return build_skill_detail_payload(registry, skill_id)
@mcp.tool
def get_pattern_by_id(id: str) -> dict[str, Any]:
"""Return one normalized pattern by stable id."""
return get_pattern_by_id_payload(REGISTRY, id)
@mcp.tool
def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
"""Return the canonical skill document payload for a stable skill id."""
if skill_id not in REGISTRY.skills_by_id:
return {"found": False, "id": skill_id}
return {
"found": True,
"document": read_skill_document(REGISTRY, skill_id),
}
@mcp.tool
def search_prompts(
query: str = "",
tags: list[str] | None = None,
skip: int = 0,
limit: int = 20,
) -> dict[str, Any]:
"""Search prompt metadata with optional tags and pagination."""
return search_prompts_payload(
REGISTRY,
query=query,
tags=tags,
skip=skip,
limit=limit,
@mcp.resource(
"resource://skills/{skill_id}/document",
mime_type="text/markdown",
tags={"skill-doc"},
annotations=_ro_annotations(),
)
def skill_document(skill_id: str) -> dict[str, str]:
return read_skill_document(registry, skill_id)
@mcp.resource(
"resource://skills/{skill_id}/references/{ref_id}",
mime_type="text/markdown",
tags={"reference"},
annotations=_ro_annotations(),
)
def skill_reference(skill_id: str, ref_id: str) -> dict[str, str]:
return read_skill_reference(registry, skill_id=skill_id, ref_id=ref_id)
@mcp.resource(
"resource://docs/{path*}",
mime_type="text/markdown",
tags={"docs"},
annotations=_ro_annotations(),
)
def docs_markdown(path: str) -> dict[str, str]:
return read_docs_markdown_path(registry, path)
@mcp.resource(
"resource://catalog/prompts_index",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def prompts_index() -> dict[str, Any]:
return build_prompts_index_payload(registry)
@mcp.resource(
"resource://catalog/prompts_index{?q,tag,cursor,limit}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def prompts_index_query(
q: str | None = None,
tag: str | None = None,
cursor: str | None = None,
limit: int | None = None,
) -> dict[str, Any]:
return build_prompts_index_payload(
registry,
query=q,
tag=tag,
cursor=cursor,
limit=limit,
)
@mcp.resource(
"resource://catalog/prompts/{prompt_id}",
mime_type="application/json",
tags={"catalog"},
annotations=_ro_annotations(),
)
def prompt_detail(prompt_id: str) -> dict[str, Any]:
return build_prompt_detail_payload(registry, prompt_id)
@mcp.resource(
"resource://prompts/{prompt_id}/document",
mime_type="text/markdown",
tags={"prompt-doc"},
annotations=_ro_annotations(),
)
def prompt_document(prompt_id: str) -> dict[str, str]:
return read_prompt_document(registry, prompt_id)
@mcp.tool
def search_patterns(
query: str = "",
tags: list[str] | None = None,
skip: int = 0,
limit: int = 20,
) -> dict[str, Any]:
"""Search normalized pattern metadata with optional tags and pagination."""
return search_patterns_payload(
registry,
query=query,
tags=tags,
skip=skip,
limit=limit,
)
@mcp.tool
def get_pattern_by_id(id: str) -> dict[str, Any]:
"""Return one normalized pattern by stable id."""
return get_pattern_by_id_payload(registry, id)
@mcp.tool
def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
"""Return the canonical skill document payload for a stable skill id."""
if skill_id not in registry.skills_by_id:
return {"found": False, "id": skill_id}
return {
"found": True,
"document": read_skill_document(registry, skill_id),
}
@mcp.tool
def search_prompts(
query: str = "",
tags: list[str] | None = None,
skip: int = 0,
limit: int = 20,
) -> dict[str, Any]:
"""Search prompt metadata with optional tags and pagination."""
return search_prompts_payload(
registry,
query=query,
tags=tags,
skip=skip,
limit=limit,
)
@mcp.tool
def get_prompt_by_id(prompt_id: str) -> dict[str, Any]:
"""Return one prompt by stable id."""
return get_prompt_by_id_payload(registry, prompt_id)
@mcp.tool
def get_prompt_by_id(prompt_id: str) -> dict[str, Any]:
"""Return one prompt by stable id."""
return get_prompt_by_id_payload(REGISTRY, prompt_id)
_install_tool_fallback_transforms()
_register_prompt_objects()
def create_mcp() -> FastMCP:
registry = get_docs_registry()
mcp = FastMCP("personal-mcp", on_duplicate="error")
_register_components(mcp, registry)
_register_prompt_objects(mcp, registry)
_install_tool_fallback_transforms(mcp)
return mcp