started prompt mechanics

This commit is contained in:
John Lancaster
2026-06-20 20:27:32 -05:00
parent 098a2418ee
commit f8e0c14d46
12 changed files with 809 additions and 19 deletions
+168
View File
@@ -1,6 +1,8 @@
from __future__ import annotations
import os
import re
from inspect import Parameter, Signature
from typing import Any
from fastmcp import FastMCP
@@ -8,15 +10,20 @@ from fastmcp.server.transforms import ResourcesAsTools
from fastmcp.server.transforms.search import BM25SearchTransform, RegexSearchTransform
from personal_mcp.catalog.server import (
build_prompt_detail_payload,
build_prompts_index_payload,
build_skill_detail_payload,
build_skills_index_payload,
get_pattern_by_id_payload,
get_prompt_by_id_payload,
search_patterns_payload,
search_prompts_payload,
)
from personal_mcp.skills.document_loader import (
DocsRegistry,
load_docs_registry,
read_docs_markdown_path,
read_prompt_document,
read_skill_document,
read_skill_reference,
)
@@ -77,6 +84,70 @@ def _ro_annotations() -> dict[str, bool]:
}
def _render_prompt_markdown(content: str, arguments: dict[str, Any]) -> str:
rendered = content
for key, value in arguments.items():
rendered = rendered.replace(f"{{{{{key}}}}}", str(value))
return rendered
def _python_type(prompt_arg_type: str) -> type[Any]:
if prompt_arg_type == "string":
return str
if prompt_arg_type == "number":
return float
if prompt_arg_type == "integer":
return int
if prompt_arg_type == "boolean":
return bool
if prompt_arg_type == "array":
return list
if prompt_arg_type == "object":
return dict
return str
def _register_prompt_objects() -> None:
for prompt_id in REGISTRY.prompts_in_load_order:
prompt = REGISTRY.prompts_by_id[prompt_id]
annotations: dict[str, Any] = {}
params: list[Parameter] = []
for arg_name, arg in sorted(prompt.arguments.items()):
arg_type = _python_type(arg.type)
annotations[arg_name] = arg_type
if arg.required:
default = Parameter.empty
else:
default = arg.default
params.append(
Parameter(
arg_name,
kind=Parameter.KEYWORD_ONLY,
default=default,
annotation=arg_type,
)
)
signature = Signature(parameters=params, return_annotation=str)
prompt_content = prompt.document_content
def prompt_handler(**kwargs: Any) -> str:
return _render_prompt_markdown(prompt_content, kwargs)
prompt_handler.__name__ = re.sub(r"[^a-zA-Z0-9_]", "_", prompt_id)
prompt_handler.__doc__ = prompt.description
prompt_handler.__annotations__ = annotations
prompt_handler.__signature__ = signature # type: ignore[attr-defined]
mcp.prompt(
prompt_handler,
name=prompt_id,
description=prompt.description,
tags=set(prompt.tags),
)
@mcp.resource(
"resource://catalog/skills_index",
mime_type="application/json",
@@ -150,6 +221,57 @@ 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 = "",
@@ -185,6 +307,29 @@ def get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
}
@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 catalog_search_patterns(
query: str = "",
@@ -213,4 +358,27 @@ def catalog_get_skill_document_by_id(skill_id: str) -> dict[str, Any]:
return get_skill_document_by_id(skill_id)
@mcp.tool
def catalog_search_prompts(
query: str = "",
tags: list[str] | None = None,
skip: int = 0,
limit: int = 20,
) -> dict[str, Any]:
"""Compatibility alias for clients expecting catalog_* tool naming."""
return search_prompts(
query=query,
tags=tags,
skip=skip,
limit=limit,
)
@mcp.tool
def catalog_get_prompt_by_id(prompt_id: str) -> dict[str, Any]:
"""Compatibility alias for clients expecting catalog_* tool naming."""
return get_prompt_by_id(prompt_id)
_install_tool_fallback_transforms()
_register_prompt_objects()