-
Notifications
You must be signed in to change notification settings - Fork 6
Add Skills as Tools reference implementation (Approach 3) #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+2,707
−3
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,12 @@ | ||
| .claude/settings.local.json | ||
|
|
||
| # Example build artifacts | ||
| node_modules/ | ||
| dist/ | ||
| __pycache__/ | ||
| *.pyc | ||
| .venv/ | ||
| *.egg-info/ | ||
|
|
||
| # Reference repos (cloned for development) | ||
| %TEMP%/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # Skills as Tools — Reference Implementation | ||
|
|
||
| > **Experimental** — This is a minimal reference implementation for evaluation by the Skills Over MCP Interest Group. Not intended for production use. | ||
|
|
||
| ## Pattern Overview | ||
|
|
||
| This example demonstrates **Approach 3** from [`docs/approaches.md`](../../docs/approaches.md): exposing agent skills via MCP tools. | ||
|
|
||
| An MCP server scans a directory for SKILL.md files and exposes two tools: | ||
|
|
||
| | Tool | Purpose | | ||
| | :--- | :--- | | ||
| | `list_skills` | Returns skill names and descriptions (progressive disclosure — summaries only, not full content) | | ||
| | `read_skill` | Accepts a skill `name` and returns the full SKILL.md content on demand | | ||
|
|
||
| This is a **model-controlled** approach: the LLM decides when to invoke skills based on tool descriptions. See [Open Question #9](../../docs/open-questions.md) for the control model discussion. | ||
|
|
||
| ## How It Works | ||
|
|
||
| ``` | ||
| ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ | ||
| │ MCP Client │────▶│ Skills as Tools │────▶│ Skill Files │ | ||
| │ (e.g. Claude│◀────│ MCP Server │◀────│ (SKILL.md) │ | ||
| │ Code) │ └──────────────────┘ └──────────────┘ | ||
| └─────────────┘ | ||
| ``` | ||
|
|
||
| 1. **Startup**: Server scans the configured skills directory for `*/SKILL.md` files | ||
| 2. **Discovery**: Parses YAML frontmatter to extract `name` and `description` | ||
| 3. **Registration**: Registers `list_skills` and `read_skill` as MCP tools; tool descriptions include available skill names | ||
| 4. **Progressive disclosure**: Agent calls `list_skills` to see what's available, then `read_skill(name)` to load full instructions only when needed | ||
| 5. **Capability declaration**: Server declares `tools.listChanged` capability (dynamic updates could be wired to a file watcher in a full implementation) | ||
|
|
||
| ## Implementations | ||
|
|
||
| Both implementations expose the same tools with the same behavior. They share the `sample-skills/` directory as test data. | ||
|
|
||
| ### TypeScript | ||
|
|
||
| **Prerequisites**: Node.js >= 18, npm | ||
|
|
||
| ```bash | ||
| cd typescript | ||
| npm install | ||
| npm run build | ||
| ``` | ||
|
|
||
| **Run with MCP Inspector**: | ||
| ```bash | ||
| npx @modelcontextprotocol/inspector node dist/index.js ../sample-skills | ||
| ``` | ||
|
|
||
| **Development mode** (no build step): | ||
| ```bash | ||
| npm run dev -- ../sample-skills | ||
| ``` | ||
|
|
||
| ### Python | ||
|
|
||
| **Prerequisites**: Python >= 3.10, pip (or uv) | ||
|
|
||
| ```bash | ||
| cd python | ||
| pip install -e . | ||
| ``` | ||
|
|
||
| **Run with MCP Inspector**: | ||
| ```bash | ||
| npx @modelcontextprotocol/inspector -- python -m skills_as_tools.server ../sample-skills | ||
| ``` | ||
|
|
||
| ## Security Features | ||
|
|
||
| Both implementations include: | ||
|
|
||
| - **Path traversal protection** — Resolved paths are checked against the skills directory boundary using `realpathSync` (TS) / `Path.resolve()` (Python). Symlink escapes are detected. | ||
| - **Skill name validation** — `read_skill` looks up names by key in the discovered skills map. User input is never used to construct file paths. | ||
| - **File size limits** — Files larger than 1MB are skipped during discovery and rejected on read. | ||
| - **Allowlisted file types** — Only `.md` files are readable. | ||
| - **Safe YAML parsing** — Python uses `yaml.safe_load()` to prevent code execution. TypeScript uses the `yaml` package which is safe by default. | ||
|
|
||
| ## Sample Skills | ||
|
|
||
| Two sample skills are included in `sample-skills/` for testing: | ||
|
|
||
| | Skill | Description | Notes | | ||
| | :--- | :--- | :--- | | ||
| | `git-commit-review` | Review commits for quality and conventional format | Standalone skill (no extra files) | | ||
| | `code-review` | Structured code review methodology | Includes `references/REFERENCE.md` (progressive disclosure) | | ||
|
|
||
| ## Key Design Decisions | ||
|
|
||
| - **Tools over resources**: Tools are model-controlled — the LLM decides when to invoke them based on descriptions. Resources are application-controlled, which experimental findings show leads to lower utilization (see [`docs/experimental-findings.md`](../../docs/experimental-findings.md)). | ||
| - **Two tools, not one**: Separating `list_skills` from `read_skill` enables progressive disclosure — the model sees summaries first and only loads full content when needed, saving context tokens. | ||
| - **Skill manifest in tool description**: The `list_skills` tool description includes available skill names, so the model knows what's available without making a tool call (following the skilljack pattern). | ||
|
|
||
| ## What This Example Intentionally Omits | ||
|
|
||
| - `skill://` URI resources (see: future skills-as-resources example) | ||
| - File watching / live dynamic updates (capability is declared but not wired) | ||
| - MCP Prompts for explicit skill invocation | ||
| - GitHub sync, configuration UI | ||
| - `skill-resource` tool for reading files within skill directories | ||
|
|
||
| ## Relationship to Other Approaches | ||
|
|
||
| | Approach | How it differs | | ||
| | :--- | :--- | | ||
| | **1. Skills as Primitives** (SEP-2076) | Uses dedicated `skills/list` and `skills/get` protocol methods instead of tools | | ||
| | **3. Skills as Tools** (this example) | Uses existing MCP tools primitive — no protocol changes needed | | ||
| | **5. Server Instructions** | Uses server instructions to point to resources instead of tools | | ||
| | **6. Convention** | This example could become part of a documented convention pattern | | ||
|
|
||
| ## Inspirations and Attribution | ||
|
|
||
| This reference implementation is original code inspired by patterns from: | ||
|
|
||
| - **[skilljack-mcp](https://github.com/olaservo/skilljack-mcp)** by [Ola Hungerford](https://github.com/olaservo) — Tool description embedding, progressive disclosure, path security, dynamic updates | ||
| - **[skills-over-mcp](https://github.com/keithagroves/skills-over-mcp)** by [Keith Groves](https://github.com/keithagroves) — Resource-based skill exposure, skill URI schemes |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| [project] | ||
| name = "skills-as-tools-example" | ||
| version = "0.1.0" | ||
| description = "Minimal reference implementation: Skills as MCP Tools (Approach 3)" | ||
| requires-python = ">=3.10" | ||
| dependencies = [ | ||
| "mcp>=1.0.0", | ||
| "pyyaml>=6.0", | ||
| ] | ||
| license = "Apache-2.0" | ||
|
|
||
| [project.scripts] | ||
| skills-as-tools = "skills_as_tools.server:main" | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/skills_as_tools"] |
Empty file.
92 changes: 92 additions & 0 deletions
92
examples/skills-as-tools/python/src/skills_as_tools/server.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| """ | ||
| Skills as Tools — MCP Server (Python) | ||
|
|
||
| A minimal reference implementation demonstrating Approach 3 from the | ||
| Skills Over MCP Interest Group: exposing agent skills via MCP tools. | ||
|
|
||
| Exposes two tools: | ||
| - list_skills: Returns skill names and descriptions (progressive disclosure) | ||
| - read_skill: Returns the full SKILL.md content for a named skill | ||
|
|
||
| Inspired by: | ||
| - skilljack-mcp by Ola Hungerford (https://github.com/olaservo/skilljack-mcp) | ||
| - skills-over-mcp by Keith Groves (https://github.com/keithagroves/skills-over-mcp) | ||
|
|
||
| License: Apache-2.0 | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| from mcp.server.fastmcp import FastMCP | ||
|
|
||
| from .skill_discovery import discover_skills, load_skill_content | ||
|
|
||
| # Resolve skills directory from CLI arg or default to ../sample-skills | ||
| if len(sys.argv) > 1: | ||
| skills_dir = str(Path(sys.argv[1]).resolve()) | ||
| else: | ||
| skills_dir = str(Path(__file__).resolve().parent.parent.parent.parent / "sample-skills") | ||
|
|
||
| # Discover skills at startup | ||
| skill_map = discover_skills(skills_dir) | ||
| skill_names = list(skill_map.keys()) | ||
|
|
||
| print( | ||
| f"[skills-as-tools] Discovered {len(skill_map)} skill(s): " | ||
| f"{', '.join(skill_names) or 'none'}", | ||
| file=sys.stderr, | ||
| ) | ||
|
|
||
| # Create MCP server | ||
| mcp = FastMCP( | ||
| name="skills-as-tools-example", | ||
| ) | ||
|
|
||
|
|
||
| @mcp.tool( | ||
| description=( | ||
| "List all available skills with their names and descriptions. " | ||
| f"Currently available: {', '.join(skill_names) or 'none'}" | ||
| ), | ||
| ) | ||
| def list_skills() -> str: | ||
| """List all available skills (progressive disclosure — summaries only).""" | ||
| summaries = [ | ||
| {"name": s.name, "description": s.description} | ||
| for s in skill_map.values() | ||
| ] | ||
| return json.dumps(summaries, indent=2) | ||
|
|
||
|
|
||
| @mcp.tool( | ||
| description=( | ||
| "Read the full instructions for a specific skill by name. " | ||
| "Returns the complete SKILL.md content with step-by-step guidance." | ||
| ), | ||
| ) | ||
| def read_skill(name: str) -> str: | ||
| """Read a skill's full SKILL.md content by name.""" | ||
| # Security: lookup by key only — never construct paths from user input | ||
| skill = skill_map.get(name) | ||
|
|
||
| if not skill: | ||
| available = ", ".join(skill_names) or "none" | ||
| return f'Skill "{name}" not found. Available skills: {available}' | ||
|
|
||
| try: | ||
| return load_skill_content(skill.path, skills_dir) | ||
| except (OSError, ValueError) as exc: | ||
| return f'Failed to load skill "{name}": {exc}' | ||
|
|
||
|
|
||
| def main() -> None: | ||
| """Entry point: run the MCP server via stdio transport.""" | ||
| mcp.run(transport="stdio") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
read_skillneed to return all content in the skill folder given a skill can get pretty complex.Also we can think about whether it should be
readorinstallhere.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually when I looked at your PR about adding skills as resources, that solved this problem very well with the file hierarchy!
Thanks for driving all these and they will be good discussion points.