better vscode integration

This commit is contained in:
John Lancaster
2026-08-08 00:05:58 -05:00
parent a157489634
commit 9be7c27410
7 changed files with 120 additions and 17 deletions
+63 -2
View File
@@ -1,24 +1,53 @@
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
_SERVER_INSTRUCTIONS = """Personal development guidance exposed as native MCP resources and prompts.
Use prompts for parameterized workflows. For task-specific guidance, list resources, select one
skill://<name>/SKILL.md by description, and read its manifest only when supporting detail is needed.
Read-only compatibility tools expose the same resource catalog to clients without native resource access.
"""
_SERVER_ICON = Icon(
src=(
"data:image/svg+xml;base64,"
"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+"
"PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iOCIgZmlsbD0iIzE2N0Q4RCIvPjxwYXRoIGQ9"
"Ik0xOCAxNmgyMGw4IDh2MjRIMTh6IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lk"
"dGg9IjQiLz48cGF0aCBkPSJNMzggMTZ2MTBoOE0yNSAzM2gxNE0yNSA0MGgxNCIgZmlsbD0ibm9uZSIg"
"c3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI0Ii8+PC9zdmc+"
),
mime_type="image/svg+xml",
sizes=["64x64"],
)
def _ro_annotations() -> dict[str, bool]:
return {
"readOnlyHint": True,
"idempotentHint": True,
"openWorldHint": False,
}
def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
@mcp.resource(
"resource://docs/{path*}",
name="docs_markdown",
title="Documentation Markdown",
description="Read a packaged documentation page by its path relative to the docs root.",
mime_type="text/markdown",
tags={"docs"},
annotations=_ro_annotations(),
@@ -30,6 +59,7 @@ def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
def _register_resource_tools(mcp: FastMCP) -> None:
@mcp.tool(
name="list_resources",
title="List Resources",
description=(
"List available MCP resources and URI templates. Use before read_resource to discover skill guidance."
),
@@ -61,6 +91,7 @@ def _register_resource_tools(mcp: FastMCP) -> None:
@mcp.tool(
name="read_resource",
title="Read Resource",
description="Read a resource URI returned by list_resources, including skill files, manifests, and references.",
annotations=_ro_annotations(),
)
@@ -69,11 +100,41 @@ def _register_resource_tools(mcp: FastMCP) -> None:
return result.model_dump(mode="json", exclude_none=True)
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", on_duplicate="error")
mcp = FastMCP(
"personal-mcp",
instructions=_SERVER_INSTRUCTIONS,
icons=[_SERVER_ICON],
on_duplicate="error",
)
_register_components(mcp, registry)
mcp.add_provider(create_prompts_provider())
prompts_provider = create_prompts_provider()
mcp.add_provider(prompts_provider)
mcp.add_provider(create_skills_provider())
_register_resource_tools(mcp)
_register_prompt_completions(mcp, prompts_provider)
return mcp