migration
This commit is contained in:
+2
-172
@@ -1,66 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from inspect import Parameter
|
||||
from inspect import Signature
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import ResourcesAsTools
|
||||
from fastmcp.server.transforms.search import BM25SearchTransform
|
||||
from fastmcp.server.transforms.search import RegexSearchTransform
|
||||
|
||||
from personal_mcp.catalog.server import build_prompt_detail_payload
|
||||
from personal_mcp.catalog.server import build_prompts_index_payload
|
||||
from personal_mcp.catalog.server import get_prompt_by_id_payload
|
||||
from personal_mcp.catalog.server import search_prompts_payload
|
||||
from personal_mcp.prompts import create_prompts_provider
|
||||
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.skills import create_skills_provider
|
||||
|
||||
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")
|
||||
|
||||
|
||||
def _parse_positive_int(value: str, *, env_name: str) -> int:
|
||||
try:
|
||||
parsed = int(value)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"{env_name} must be an integer") from exc
|
||||
if parsed <= 0:
|
||||
raise ValueError(f"{env_name} must be greater than zero")
|
||||
return parsed
|
||||
|
||||
|
||||
def _install_tool_fallback_transforms(mcp: FastMCP) -> None:
|
||||
# Expose list_resources/read_resource for tool-only clients.
|
||||
mcp.add_transform(ResourcesAsTools(mcp))
|
||||
|
||||
if TOOL_SEARCH_MODE in {"", "none"}:
|
||||
return
|
||||
|
||||
max_results = _parse_positive_int(
|
||||
TOOL_SEARCH_MAX_RESULTS,
|
||||
env_name="PERSONAL_MCP_TOOL_SEARCH_MAX_RESULTS",
|
||||
)
|
||||
kwargs: dict[str, Any] = {
|
||||
"max_results": max_results,
|
||||
"always_visible": ["list_resources", "read_resource"],
|
||||
}
|
||||
|
||||
if TOOL_SEARCH_MODE == "regex":
|
||||
mcp.add_transform(RegexSearchTransform(**kwargs))
|
||||
return
|
||||
if TOOL_SEARCH_MODE == "bm25":
|
||||
mcp.add_transform(BM25SearchTransform(**kwargs))
|
||||
return
|
||||
|
||||
raise ValueError("PERSONAL_MCP_TOOL_SEARCH must be one of: none, regex, bm25")
|
||||
|
||||
|
||||
def _ro_annotations() -> dict[str, bool]:
|
||||
return {
|
||||
@@ -69,54 +16,6 @@ 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 _make_prompt_handler(content: str):
|
||||
def prompt_handler(**kwargs: Any) -> str:
|
||||
return _render_prompt_markdown(content, kwargs)
|
||||
|
||||
return prompt_handler
|
||||
|
||||
|
||||
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] = []
|
||||
|
||||
for arg_name, arg in sorted(prompt.arguments.items()):
|
||||
annotations[arg_name] = str
|
||||
default = Parameter.empty if arg.required else None
|
||||
params.append(
|
||||
Parameter(
|
||||
arg_name,
|
||||
kind=Parameter.KEYWORD_ONLY,
|
||||
default=default,
|
||||
annotation=str,
|
||||
)
|
||||
)
|
||||
|
||||
signature = Signature(parameters=params, return_annotation=str)
|
||||
|
||||
prompt_handler = _make_prompt_handler(prompt.document_content)
|
||||
|
||||
prompt_handler.__name__ = re.sub(r"[^a-zA-Z0-9_]", "_", prompt_id)
|
||||
prompt_handler.__doc__ = prompt.description
|
||||
prompt_handler.__annotations__ = annotations
|
||||
cast(Any, prompt_handler).__signature__ = signature
|
||||
mcp.prompt(
|
||||
prompt_handler,
|
||||
name=prompt_id,
|
||||
description=prompt.description,
|
||||
tags=set(prompt.tags),
|
||||
)
|
||||
|
||||
|
||||
def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
|
||||
@mcp.resource(
|
||||
"resource://docs/{path*}",
|
||||
@@ -127,80 +26,11 @@ def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
|
||||
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_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)
|
||||
|
||||
|
||||
def create_mcp() -> FastMCP:
|
||||
registry = get_docs_registry()
|
||||
mcp = FastMCP("personal-mcp", on_duplicate="error")
|
||||
_register_components(mcp, registry)
|
||||
_register_prompt_objects(mcp, registry)
|
||||
mcp.add_provider(create_prompts_provider())
|
||||
mcp.add_provider(create_skills_provider())
|
||||
_install_tool_fallback_transforms(mcp)
|
||||
return mcp
|
||||
|
||||
Reference in New Issue
Block a user