big rework

This commit is contained in:
John Lancaster
2026-08-29 20:10:30 -05:00
parent 73eb490537
commit de95477480
30 changed files with 558 additions and 971 deletions
+27 -55
View File
@@ -1,18 +1,12 @@
from __future__ import annotations
from fastmcp import FastMCP
from mcp_types import CompletionArgument
from mcp_types import CompletionContext
from mcp_types import Icon
from mcp_types import PromptReference
from personal_mcp.prompts import create_prompts_provider
from personal_mcp.prompts.models import MarkdownPrompt
from personal_mcp.prompts.provider import MarkdownPromptsProvider
from personal_mcp.registry.load import get_docs_registry
from personal_mcp.registry.load import read_docs_markdown_path
from personal_mcp.registry.models import DocsRegistry
from personal_mcp.skills import create_skills_provider
from .prompts.provider import prompt_lifespan
from .registry.load import get_docs_registry
from .registry.load import read_docs_markdown_path
from .skills import skill_lifespan
_SERVER_INSTRUCTIONS = """Personal development guidance exposed as native MCP resources and prompts.
@@ -33,15 +27,22 @@ _SERVER_ICON = Icon(
)
def _ro_annotations() -> dict[str, bool]:
return {
"readOnlyHint": True,
"idempotentHint": True,
"openWorldHint": False,
}
def run_stdio() -> None:
"""Create the MCP server and expose it over stdio"""
create_mcp().run()
def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
def create_mcp() -> FastMCP:
mcp = FastMCP(
"personal-mcp",
instructions=_SERVER_INSTRUCTIONS,
icons=[_SERVER_ICON],
on_duplicate="error",
lifespan=skill_lifespan | prompt_lifespan,
)
registry = get_docs_registry()
@mcp.resource(
"resource://docs/{path*}",
name="docs_markdown",
@@ -49,46 +50,17 @@ def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
description="Read a packaged documentation page by its path relative to the docs root.",
mime_type="text/markdown",
tags={"docs"},
annotations=_ro_annotations(),
annotations={
"readOnlyHint": True,
"idempotentHint": True,
"openWorldHint": False,
},
)
def docs_markdown(path: str) -> dict[str, str]:
return read_docs_markdown_path(registry, path)
def _register_prompt_completions(mcp: FastMCP, provider: MarkdownPromptsProvider) -> None:
@mcp.completion
async def complete_prompt_argument(
ref: object,
argument: CompletionArgument,
context: CompletionContext | None,
) -> list[str] | None:
del context
if not isinstance(ref, PromptReference):
return None
prompt = await provider.get_prompt(ref.name)
if not isinstance(prompt, MarkdownPrompt):
return None
definition = prompt.definitions.get(argument.name)
if definition is None or definition.choices is None:
return None
prefix = argument.value.casefold()
return [choice for choice in definition.choices if choice.casefold().startswith(prefix)]
def create_mcp() -> FastMCP:
registry = get_docs_registry()
mcp = FastMCP(
"personal-mcp",
instructions=_SERVER_INSTRUCTIONS,
icons=[_SERVER_ICON],
on_duplicate="error",
)
_register_components(mcp, registry)
prompts_provider = create_prompts_provider()
mcp.add_provider(prompts_provider)
mcp.add_provider(create_skills_provider())
_register_prompt_completions(mcp, prompts_provider)
return mcp
if __name__ == "__main__":
run_stdio()