From 467e1d3c3539d1f7075eeb62df3caa128caf0341 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:31:24 -0500 Subject: [PATCH] sten 6 implementation --- .github/prompts/plan-step1.prompt.md | 2 +- .github/prompts/plan-step3.prompt.md | 41 ++++--------------------- .github/prompts/plan-step6.prompt.md | 31 ++++++++++--------- docs/copilot.md | 34 +++++++++++++++------ docs/usage.md | 32 +++++++++++++++----- src/personal_mcp/mcp.py | 45 ++++++++++++++++++++++++++++ zensical.toml | 2 +- 7 files changed, 119 insertions(+), 68 deletions(-) diff --git a/.github/prompts/plan-step1.prompt.md b/.github/prompts/plan-step1.prompt.md index ac21600..202efb1 100644 --- a/.github/prompts/plan-step1.prompt.md +++ b/.github/prompts/plan-step1.prompt.md @@ -55,7 +55,7 @@ Rules: - No underscores, spaces, dots, or uppercase characters. - Directory name should equal `skill-id` in each committed revision. - Frontmatter `id` should equal directory name in each committed revision. -- Prefer keeping `skill-id` stable, but renames are allowed when needed if mappings are updated together. +- Treat `skill-id` as immutable after release; any rename is a breaking replacement and clients must move to the new id. Example valid ids: diff --git a/.github/prompts/plan-step3.prompt.md b/.github/prompts/plan-step3.prompt.md index 2c69e24..75bd905 100644 --- a/.github/prompts/plan-step3.prompt.md +++ b/.github/prompts/plan-step3.prompt.md @@ -72,16 +72,6 @@ Contract intent: - Resolves only inside `docs/`. - This surface is markdown-only in end state (`.md` files). -### Legacy URI Policy (Current-to-Target Transition) - -Current catalog URIs in this repo (`resource://catalog/patterns`, `resource://catalog/patterns_by_id`, `resource://catalog/skills_details`) are treated as compatibility aliases during migration. - -Rules: - -- Keep aliases only when needed for active clients. -- Prefer simple canonical URIs for new clients. -- Remove aliases once consumers have moved. - ### URI Versioning Policy Default rule: @@ -91,30 +81,14 @@ Default rule: Breaking-change rule: -- If clients are already using an older shape, provide either: - - a short-lived alias, or - - a versioned URI family such as `resource://catalog/v2/...`. -- Choose the lightest migration path that minimizes maintenance overhead. +- Breaking changes use direct replacement of the canonical URI family. +- No compatibility aliases or dual URI families are maintained in this greenfield phase. FastMCP version metadata usage: - Resource `version` metadata MAY be used for implementation/version discovery. - URI readability and maintainability remain the primary contract. -### Deprecation Policy For URIs - -When deprecating a URI: - -1. Document the replacement URI in changelog/docs. -2. Optionally return deprecation metadata while an alias exists. -3. Remove deprecated aliases when no active client needs them. - -Recommended deprecation metadata fields in resource responses: - -- `deprecated: true` -- `replacement_uri: ` -- `sunset_at: ` - ### Reference ID Compatibility Policy `ref_id` is the public identifier for a reference document, separate from file path. @@ -123,19 +97,14 @@ Rules: - Prefer keeping `ref_id` stable when practical. - File paths may change without URI churn as long as the mapped `ref_id` resolves. -- If a reference is renamed, introduce a new `ref_id`; keep an alias only if clients depend on the old id. +- If a reference is renamed, introduce a new `ref_id` and treat the old one as retired. - Avoid reusing retired `ref_id` values for unrelated content. -Alias behavior for renamed references: - -- If alias is kept, old `ref_id` continues to resolve and points to the replacement. -- Remove old alias as soon as migration is complete. - ### Invariants This Contract Guarantees - One canonical URI pattern per core capability surface. -- Fast, low-friction URI evolution with optional compatibility aliases. -- Explicit migration path for catalog URI consolidation when needed. +- Fast, low-friction URI evolution through direct replacement of canonical URIs. +- A single canonical catalog URI family with no alias maintenance overhead. - Reference mappings can evolve with minimal churn. ### Non-goals For Step 3 diff --git a/.github/prompts/plan-step6.prompt.md b/.github/prompts/plan-step6.prompt.md index c9f73cc..c991635 100644 --- a/.github/prompts/plan-step6.prompt.md +++ b/.github/prompts/plan-step6.prompt.md @@ -50,17 +50,19 @@ Rules: ### Tool Fallback Surface (Normative) -The minimal read-only fallback tool set is: +The fallback tool surface includes: -1. `search_patterns` -2. `get_pattern_by_id` -3. `get_skill_document_by_id` +1. `list_resources` +2. `read_resource` +3. `search_patterns` +4. `get_pattern_by_id` +5. `get_skill_document_by_id` Fallback order: -1. call `search_patterns` to find likely skills -2. call `get_pattern_by_id` for one selected id when detail is needed -3. call `get_skill_document_by_id` only for selected skill(s) +1. call `list_resources` to inspect canonical static/template resource surfaces +2. call `read_resource` for catalog URIs and selected skill URIs +3. use thin catalog tools only when additional metadata-first narrowing is needed Tool behavior requirements: @@ -71,14 +73,14 @@ Tool behavior requirements: ### Resources-As-Tools Compatibility Layer -Step 6 may include a resources-as-tools compatibility layer for clients that can call tools but not attach resources. +Step 6 includes a resources-as-tools compatibility layer for clients that can call tools but not attach resources. Rules: -1. If enabled, it wraps canonical resource reads rather than re-implementing content transforms. +1. It wraps canonical resource reads rather than re-implementing content transforms. 2. It preserves canonical URIs and metadata semantics. 3. It does not replace the minimal catalog tools listed above. -4. It remains optional and interoperability-driven. +4. It is interoperability-driven and remains read-only. ### Resource/Tool Parity Contract @@ -144,10 +146,11 @@ Separation-of-concerns rule: Step 6 is complete when all are true: 1. Resource-first discovery remains the documented and implemented default path. -2. The fallback tool set is minimal, read-only, and schema-aligned. -3. Fallback tool outputs map to canonical skill identities and URIs. -4. Context loading is bounded and clarifying-question behavior is documented for low-confidence cases. -5. No second content source is introduced; resources and tools resolve the same authored markdown. +2. `list_resources` and `read_resource` are available for tool-only clients. +3. Thin catalog tools remain minimal, read-only parity surfaces. +4. Fallback tool outputs map to canonical skill identities and URIs. +5. Context loading is bounded and clarifying-question behavior is documented for low-confidence cases. +6. No second content source is introduced; resources and tools resolve the same authored markdown. ### Non-goals for Step 6 diff --git a/docs/copilot.md b/docs/copilot.md index 954f55d..3983899 100644 --- a/docs/copilot.md +++ b/docs/copilot.md @@ -61,14 +61,20 @@ Use this sequence to confirm behavior: 2. fetch only selected skill documents for context 3. keep slash commands for deterministic fallback flows -When resource attachment is unavailable in the active session, use thin catalog discovery tools as operational fallback: +When resource attachment is unavailable in the active session, use ResourcesAsTools first, then thin catalog discovery tools as parity fallback: -1. `search_patterns` -2. `get_pattern_by_id` -3. `get_skill_document_by_id` +1. `list_resources` +2. `read_resource` +3. `search_patterns` +4. `get_pattern_by_id` +5. `get_skill_document_by_id` + +The first two are generated from the canonical resource surface and should be preferred in tool-only clients. These should stay read-only, minimal, and schema-aligned with catalog resources. +For very large tool catalogs, server operators can optionally enable tool search mode (`regex` or `bm25`) while keeping `list_resources` and `read_resource` pinned as always-visible fallback tools. + ## What To Type In Copilot Chat Use prompts that tell Copilot which MCP feature path to take. @@ -91,7 +97,15 @@ I attached personal-mcp catalog resources first. Use them to identify the best m ### If only tools are available -Ask Copilot to explicitly use the catalog tools. +Ask Copilot to explicitly use resource-backed tools first. + +Example resource-backed prompt: + +```text +Use personal-mcp tool fallback by first calling list_resources, then read_resource for resource://catalog/skills_index and the selected resource://skills//document URI. Use only that loaded skill context in your answer. +``` + +If needed, use the thin catalog tools. Example discovery prompt: @@ -128,7 +142,7 @@ When a task may benefit from personal-mcp skills, use this sequence: 1. Start with personal-mcp catalog discovery when the task appears to match documented implementation patterns. 2. Prefer MCP resources when the chat surface exposes resource attachment. -3. If MCP resource attachment is unavailable, use catalog tools instead. +3. If MCP resource attachment is unavailable, use `list_resources`/`read_resource` first, then thin catalog tools if needed. 4. Load only the most relevant skill document or at most 2 skill documents. 5. Treat skill documents as guidance, then reconcile them with the actual repository code before making changes. @@ -141,9 +155,11 @@ Preferred discovery order: Tool fallback order: -1. `search_patterns` -2. `get_pattern_by_id` -3. `get_skill_document_by_id` +1. `list_resources` +2. `read_resource` +3. `search_patterns` +4. `get_pattern_by_id` +5. `get_skill_document_by_id` If confidence is low after catalog discovery, ask one clarifying question before loading more skill documents. ``` diff --git a/docs/usage.md b/docs/usage.md index 9f59cda..64e6088 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -72,7 +72,7 @@ In this project, "automatic loading" should be read as a preference you express In practice, there are two reliable ways to make skill content available in chat: 1. explicit resource attachment through `Add Context > MCP Resources` or `MCP: Browse Resources` -2. MCP tool invocation such as `search_patterns` followed by `get_skill_document_by_id` +2. MCP tool invocation using `list_resources`/`read_resource` (ResourcesAsTools), with thin catalog tools as parity fallback Instruction quality and metadata quality still matter, because they influence whether Copilot recognizes that the MCP server is relevant and chooses the tool path well. @@ -146,6 +146,21 @@ Weak metadata reduces Copilot match quality and increases wrong context injectio If you skip the catalog/index step, behavior is less predictable and may either miss relevant skills or pull too much context. +## Optional Tool Search Mode + +When tool catalogs grow, FastMCP search transforms can reduce tool-list noise for tool-only clients. + +Runtime switches: + +1. `PERSONAL_MCP_TOOL_SEARCH=none|regex|bm25` (default `none`) +2. `PERSONAL_MCP_TOOL_SEARCH_MAX_RESULTS=` (default `5`) + +Behavior: + +1. `regex` uses deterministic regex matching for targeted queries. +2. `bm25` uses ranked natural-language matching. +3. `list_resources` and `read_resource` stay visible so resource-backed fallback remains primary. + ## Copilot Instruction Pattern If you want Copilot to use `personal-mcp` skill content more reliably, the instruction file should describe three things clearly: @@ -170,7 +185,7 @@ When a task may match a documented implementation pattern from `personal-mcp`: 1. Start with catalog-first discovery. 2. Prefer MCP resources when the chat surface exposes resource attachment. -3. If MCP resource attachment is unavailable, use catalog tools instead. +3. If MCP resource attachment is unavailable, use `list_resources`/`read_resource` first, then thin catalog tools if needed. 4. Load only the most relevant skill document, or at most 2 skill documents. 5. Reconcile loaded skill guidance with the actual repository code before making changes. @@ -183,9 +198,11 @@ Preferred resource order: Preferred tool fallback order: -1. `search_patterns` -2. `get_pattern_by_id` -3. `get_skill_document_by_id` +1. `list_resources` +2. `read_resource` +3. `search_patterns` +4. `get_pattern_by_id` +5. `get_skill_document_by_id` If confidence is low after discovery, ask one clarifying question before loading more context. ``` @@ -225,8 +242,9 @@ Suggested instruction policy text: 1. Start with catalog-first discovery. 2. Prefer MCP resources when the chat surface exposes resource attachment. -3. Otherwise use catalog tools to search and load one or two likely skill documents. -4. If confidence is low, ask one clarifying question before loading more. +3. Otherwise use tool fallback to load one or two likely skill documents. +4. Prefer `list_resources`/`read_resource` first when operating in tool-only clients. +5. If confidence is low, ask one clarifying question before loading more. ## Summary diff --git a/src/personal_mcp/mcp.py b/src/personal_mcp/mcp.py index 556cdba..f9038aa 100644 --- a/src/personal_mcp/mcp.py +++ b/src/personal_mcp/mcp.py @@ -4,6 +4,8 @@ import os from typing import Any from fastmcp import FastMCP +from fastmcp.server.transforms import ResourcesAsTools +from fastmcp.server.transforms.search import BM25SearchTransform, RegexSearchTransform from personal_mcp.catalog.server import ( build_skill_detail_payload, @@ -20,6 +22,8 @@ from personal_mcp.skills.document_loader import ( ) DOCS_ROOT = os.getenv("PERSONAL_MCP_DOCS_ROOT", "../../docs") +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") REGISTRY: DocsRegistry = load_docs_registry( package_anchor="personal_mcp", docs_root=DOCS_ROOT, @@ -28,6 +32,44 @@ REGISTRY: DocsRegistry = load_docs_registry( mcp = FastMCP("personal-mcp", on_duplicate="error") +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() -> 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 { "readOnlyHint": True, @@ -141,3 +183,6 @@ def get_skill_document_by_id(skill_id: str) -> dict[str, Any]: "found": True, "document": read_skill_document(REGISTRY, skill_id), } + + +_install_tool_fallback_transforms() diff --git a/zensical.toml b/zensical.toml index 5d3cae8..8ae8d2f 100644 --- a/zensical.toml +++ b/zensical.toml @@ -92,7 +92,7 @@ nav = [ { "Arch" = "skills/nicegui/references/architecture.md" }, { "Sources" = "skills/nicegui/references/source-documentation.md" }, ] }, - { "NiceGUI UI" = [ + { "NiceGUI Fine-Tuning" = [ { "Overview" = "skills/nicegui-ui-customization/SKILL.md" }, { "Style" = "skills/nicegui-ui-customization/references/architecture-and-styling.md" }, { "Flows" = "skills/nicegui-ui-customization/references/interaction-patterns.md" },