started prompt mechanics
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user