mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 14:04:18 +02:00
Add Skills Provider for exposing agent skills as MCP resources (#2944)
This commit is contained in:
parent
c0ef90e713
commit
16ffc9432f
18 changed files with 2398 additions and 0 deletions
104
examples/skills/README.md
Normal file
104
examples/skills/README.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Skills Provider Example
|
||||
|
||||
This example demonstrates how to expose agent skills (like Claude Code skills) as MCP resources.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
skills/
|
||||
├── README.md # This file
|
||||
├── server.py # MCP server that exposes skills
|
||||
├── client.py # Example client that discovers and reads skills
|
||||
└── sample_skills/ # Example skills directory
|
||||
├── pdf-processing/
|
||||
│ ├── SKILL.md # Main skill file
|
||||
│ └── reference.md # Supporting documentation
|
||||
└── code-review/
|
||||
└── SKILL.md # Main skill file
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
1. Start the server:
|
||||
```bash
|
||||
uv run python examples/skills/server.py
|
||||
```
|
||||
|
||||
2. In another terminal, run the client:
|
||||
```bash
|
||||
uv run python examples/skills/client.py
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The skills provider system has a two-layer architecture:
|
||||
|
||||
- **`SkillProvider`** - Handles a single skill folder, exposing its files as resources
|
||||
- **`SkillsDirectoryProvider`** - Scans a directory, creates a `SkillProvider` per folder
|
||||
- **`ClaudeSkillsProvider`** - Convenience subclass for Claude Code skills (~/.claude/skills/)
|
||||
|
||||
For each skill, the provider exposes:
|
||||
- A **Resource** for the main file (`skill://{name}/SKILL.md`)
|
||||
- A **Resource** for a synthetic manifest (`skill://{name}/_manifest`)
|
||||
- Supporting files via **ResourceTemplate** or **Resources** (configurable)
|
||||
|
||||
### Progressive Disclosure
|
||||
|
||||
When a client lists resources, they see skill names and descriptions (from frontmatter) without fetching the full content. This keeps the discovery cost low.
|
||||
|
||||
By default, supporting files are exposed via ResourceTemplate (hidden from `list_resources()`). Set `supporting_files="resources"` to make them visible:
|
||||
|
||||
```python
|
||||
SkillsDirectoryProvider(roots=skills_dir, supporting_files="resources")
|
||||
```
|
||||
|
||||
### The Manifest
|
||||
|
||||
The `_manifest` resource provides a JSON listing of all files in a skill:
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "pdf-processing",
|
||||
"files": [
|
||||
{"path": "SKILL.md", "size": 1234, "hash": "sha256:abc..."},
|
||||
{"path": "reference.md", "size": 5678, "hash": "sha256:def..."}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This enables clients to download entire skills for local use.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Single Skill
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.providers.skills import SkillProvider
|
||||
|
||||
mcp = FastMCP("My Skill")
|
||||
mcp.add_provider(SkillProvider(Path.home() / ".claude/skills/pdf-processing"))
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
### All Skills in a Directory
|
||||
|
||||
```python
|
||||
from fastmcp.server.providers.skills import SkillsDirectoryProvider
|
||||
|
||||
mcp = FastMCP("Skills")
|
||||
mcp.add_provider(SkillsDirectoryProvider(roots=Path.home() / ".claude" / "skills"))
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
### Claude Code Skills (default location)
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.providers.skills import ClaudeSkillsProvider
|
||||
|
||||
mcp = FastMCP("My Skills")
|
||||
mcp.add_provider(ClaudeSkillsProvider()) # Uses ~/.claude/skills/
|
||||
mcp.run()
|
||||
```
|
||||
66
examples/skills/client.py
Normal file
66
examples/skills/client.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""Example: Skills Client
|
||||
|
||||
This example shows how to discover and download skills from a skills server.
|
||||
|
||||
Run this client (it starts its own server internally):
|
||||
uv run python examples/skills/client.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp import Client
|
||||
from fastmcp.server.providers.skills import SkillsDirectoryProvider
|
||||
|
||||
|
||||
async def main():
|
||||
# Create a skills provider pointing at our sample skills
|
||||
skills_dir = Path(__file__).parent / "sample_skills"
|
||||
provider = SkillsDirectoryProvider(roots=skills_dir)
|
||||
|
||||
# Connect to a FastMCP server with this provider
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Skills Server")
|
||||
mcp.add_provider(provider)
|
||||
|
||||
async with Client(mcp) as client:
|
||||
print("Connected to skills server\n")
|
||||
|
||||
# List available resources
|
||||
print("=== Available Resources ===")
|
||||
resources = await client.list_resources()
|
||||
for r in resources:
|
||||
print(f" {r.uri}")
|
||||
if r.description:
|
||||
print(f" Description: {r.description}")
|
||||
print()
|
||||
|
||||
# List resource templates
|
||||
print("=== Resource Templates ===")
|
||||
templates = await client.list_resource_templates()
|
||||
for t in templates:
|
||||
print(f" {t.uriTemplate}")
|
||||
print()
|
||||
|
||||
# Read a skill's main file
|
||||
print("=== Reading pdf-processing/SKILL.md ===")
|
||||
result = await client.read_resource("skill://pdf-processing/SKILL.md")
|
||||
print(result[0].text[:500] + "...\n")
|
||||
|
||||
# Read the manifest to see all files
|
||||
print("=== Reading pdf-processing/_manifest ===")
|
||||
result = await client.read_resource("skill://pdf-processing/_manifest")
|
||||
manifest = json.loads(result[0].text)
|
||||
print(json.dumps(manifest, indent=2))
|
||||
print()
|
||||
|
||||
# Read a supporting file via template
|
||||
print("=== Reading pdf-processing/reference.md ===")
|
||||
result = await client.read_resource("skill://pdf-processing/reference.md")
|
||||
print(result[0].text[:500] + "...\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
40
examples/skills/sample_skills/code-review/SKILL.md
Normal file
40
examples/skills/sample_skills/code-review/SKILL.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
description: Review code for quality, maintainability, and correctness
|
||||
version: "1.0.0"
|
||||
tags: [code, review, quality]
|
||||
---
|
||||
|
||||
# Code Review Skill
|
||||
|
||||
This skill guides you through conducting thorough code reviews.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
When reviewing code, consider:
|
||||
|
||||
### Correctness
|
||||
- Does the code do what it's supposed to do?
|
||||
- Are edge cases handled?
|
||||
- Are there any obvious bugs?
|
||||
|
||||
### Maintainability
|
||||
- Is the code easy to understand?
|
||||
- Are variable and function names descriptive?
|
||||
- Is there appropriate documentation?
|
||||
|
||||
### Performance
|
||||
- Are there any obvious performance issues?
|
||||
- Are expensive operations cached when appropriate?
|
||||
- Are database queries efficient?
|
||||
|
||||
### Security
|
||||
- Is user input validated?
|
||||
- Are there any injection vulnerabilities?
|
||||
- Are secrets properly managed?
|
||||
|
||||
## Giving Feedback
|
||||
|
||||
- Be specific and actionable
|
||||
- Explain *why* something should change
|
||||
- Suggest alternatives, don't just criticize
|
||||
- Acknowledge good work too
|
||||
28
examples/skills/sample_skills/pdf-processing/SKILL.md
Normal file
28
examples/skills/sample_skills/pdf-processing/SKILL.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
description: Extract text from PDFs, fill forms, and merge documents
|
||||
version: "1.0.0"
|
||||
tags: [document, pdf, extraction]
|
||||
---
|
||||
|
||||
# PDF Processing Skill
|
||||
|
||||
This skill helps you work with PDF documents.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Extract text content from PDF files
|
||||
- Fill form fields in PDF documents
|
||||
- Merge multiple PDFs into one
|
||||
- Split PDFs into separate pages
|
||||
|
||||
## Usage
|
||||
|
||||
When working with PDFs, use the appropriate tools:
|
||||
|
||||
1. For text extraction: Read the PDF and parse its content
|
||||
2. For forms: Identify form fields and fill them programmatically
|
||||
3. For merging: Combine multiple documents in the desired order
|
||||
|
||||
## Additional Resources
|
||||
|
||||
See [reference.md](reference.md) for detailed API documentation.
|
||||
47
examples/skills/sample_skills/pdf-processing/reference.md
Normal file
47
examples/skills/sample_skills/pdf-processing/reference.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# PDF Processing Reference
|
||||
|
||||
## Text Extraction
|
||||
|
||||
To extract text from a PDF:
|
||||
|
||||
```python
|
||||
from pypdf import PdfReader
|
||||
|
||||
reader = PdfReader("document.pdf")
|
||||
for page in reader.pages:
|
||||
text = page.extract_text()
|
||||
print(text)
|
||||
```
|
||||
|
||||
## Form Filling
|
||||
|
||||
PDF forms can be filled using the form fields API:
|
||||
|
||||
```python
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
reader = PdfReader("form.pdf")
|
||||
writer = PdfWriter()
|
||||
|
||||
writer.append(reader)
|
||||
writer.update_page_form_field_values(
|
||||
writer.pages[0],
|
||||
{"field_name": "field_value"}
|
||||
)
|
||||
|
||||
with open("filled_form.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
## Merging PDFs
|
||||
|
||||
```python
|
||||
from pypdf import PdfWriter
|
||||
|
||||
writer = PdfWriter()
|
||||
writer.append("doc1.pdf")
|
||||
writer.append("doc2.pdf")
|
||||
|
||||
with open("merged.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
49
examples/skills/server.py
Normal file
49
examples/skills/server.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""Example: Skills Provider Server
|
||||
|
||||
This example shows how to expose agent skills as MCP resources.
|
||||
Skills can be discovered, browsed, and downloaded by any MCP client.
|
||||
|
||||
Run this server:
|
||||
uv run python examples/skills/server.py
|
||||
|
||||
Then use the client example to interact with it:
|
||||
uv run python examples/skills/client.py
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.providers.skills import (
|
||||
SkillsDirectoryProvider,
|
||||
)
|
||||
|
||||
# Create server
|
||||
mcp = FastMCP("Skills Server")
|
||||
|
||||
# Option 1: Load a single skill
|
||||
# mcp.add_provider(SkillProvider(Path.home() / ".claude/skills/pdf-processing"))
|
||||
|
||||
# Option 2: Load all skills from a custom directory
|
||||
skills_dir = Path(__file__).parent / "sample_skills"
|
||||
mcp.add_provider(SkillsDirectoryProvider(roots=skills_dir, reload=True))
|
||||
|
||||
# Option 3: Load skills from a platform's default location
|
||||
# mcp.add_provider(ClaudeSkillsProvider()) # ~/.claude/skills/
|
||||
|
||||
# Option 4: Load from multiple directories (in precedence order)
|
||||
# mcp.add_provider(SkillsDirectoryProvider(roots=[
|
||||
# Path.cwd() / ".claude/skills", # Project-level first
|
||||
# Path.home() / ".claude/skills", # User-level fallback
|
||||
# ]))
|
||||
|
||||
# Other vendor providers available:
|
||||
# - CursorSkillsProvider() → ~/.cursor/skills/
|
||||
# - VSCodeSkillsProvider() → ~/.copilot/skills/
|
||||
# - CodexSkillsProvider() → ~/.codex/skills/
|
||||
# - GeminiSkillsProvider() → ~/.gemini/skills/
|
||||
# - GooseSkillsProvider() → ~/.config/agents/skills/
|
||||
# - CopilotSkillsProvider() → ~/.copilot/skills/
|
||||
# - OpenCodeSkillsProvider() → ~/.config/opencode/skills/
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
Loading…
Add table
Add a link
Reference in a new issue