agentskills-mcp
by janacibron
README.md
# agentskills-modelcontextprotocol
An MCP (Model Context Protocol) server that exposes a catalog of "skills" to LLM agents. Each skill is a directory containing a `SKILL.md` (YAML frontmatter + Markdown body) plus optional `references/`, `scripts/`, and `assets/` subdirectories. The server lets an LLM discover which skills exist, fetch their contents on demand, and read their supporting files.
Built on [FastMCP](https://gofastmcp.com/) 2.x. Designed for the Claude Desktop, VS Code, and custom MCP client ecosystem.
---
## Architecture
```
MCP Client (LLM Agent)
│ MCP Protocol (stdio / streamable-http)
▼
create_mcp_server() ← Factory entry
│ registers tools · mounts resources · wires SkillRegistry
▼
┌─────────────────────────────────────────────┐
│ Server Surface │
│ • 10 Tools (LLM-invoked, on-demand) │
│ • 3 Resources (URI-addressable context) │
└─────────────────────────────────────────────┘
│ delegates lookups
▼
SkillRegistry ← validates frontmatter · LRU cache by (skill_id, mtime)
│ resolves via abstract interface
▼
SkillProvider (interface) ← list() · read() · stat() · list_subdir() · read_subfile()
│
├─ LocalFileSystemSkillProvider (reads ./skills)
└─ GitSkillProvider (future — pulls from remote git repo)
│
▼
Filesystem (./skills/<skill_id>/)
```
See [`mcp-server-architecture.png`](./mcp-server-architecture.png) for the full visual, or [`mcp-architecture.mmd`](./mcp-architecture.mmd) for the editable Mermaid source (renders natively on GitHub).
---
## Project structure
```
.
├── mcp_server.py # FastMCP wiring: create_mcp_server(), all 10 tools, 3 resources, CLI entry point
├── skill_provider.py # SkillProvider ABC + LocalFileSystemSkillProvider + GitSkillProvider stub
├── discovery_tools_spec.md # Spec for list_skills() / search_skills() + frontmatter schema
├── pyproject.toml # Package metadata, deps, entry point, tooling config (ruff/mypy/pytest)
├── requirements.txt # Runtime deps (pip install -r)
├── requirements-dev.txt # Dev/test deps (pytest, ruff, mypy, ipython)
├── mcp-server-architecture.png # Polished architecture diagram (Playwright + CSS)
├── mcp-architecture.mmd # Same diagram as Mermaid source (for README embedding)
├── mcp-architecture.mermaid.png # Mermaid rendering (tall/narrow — prefer the .png above)
└── README.md # This file
```
---
## Installation
### Option A — Install from source (recommended)
```bash
cd /path/to/agentskills-modelcontextprotocol
pip install -e . # editable install — pick up code changes automatically
pip install -e '.[dev]' # also installs pytest, ruff, mypy, ipython
```
This registers the `agentskills-mcp` command-line entry point (see Usage below).
### Option B — Plain requirements.txt
```bash
pip install -r requirements.txt # runtime deps only
pip install -r requirements-dev.txt # add dev/test tools
```
### Runtime dependencies
| Package | Version constraint | Why |
|---------|-------------------|-----|
| `fastmcp` | `>=2.14,<3.0` | MCP server framework |
| `pydantic` | `>=2.10,<3.0` | Response schemas (`SkillSummary`, `ListSkillsResult`, etc.) |
| `pyyaml` | `>=6.0,<7.0` | Frontmatter parsing in `skill_provider.parse_frontmatter()` |
Python 3.10+ required (uses `slots=True` dataclasses and `from __future__ import annotations`).
---
## Quick start
### Via the `agentskills-mcp` CLI (after `pip install .`)
```bash
agentskills-mcp # defaults: ./skills, stdio transport
agentskills-mcp --skills-root /opt/skills
agentskills-mcp --transport streamable-http --port 8080
agentskills-mcp --smoke-test # run in-process smoke test
```
### Via the Python API
```python
from mcp_server import create_mcp_server
mcp = create_mcp_server(skills_root="./skills")
mcp.run() # stdio transport (default — for Claude Desktop, VS Code)
```
For HTTP transport:
```python
mcp.run(transport="streamable-http", host="0.0.0.0", port=8080)
```
### Smoke tests
```bash
agentskills-mcp --smoke-test # via CLI entry point
python mcp_server.py # directly via __main__
python skill_provider.py # provider-level test (validation, path traversal, etc.)
```
---
## Skill directory convention
Each skill is a subdirectory of `./skills/` (or wherever `skills_root` points):
```
incident-response/
├── SKILL.md ← YAML frontmatter + Markdown body
├── references/ ← served by get_skill_reference()
│ └── checklist.md
├── scripts/ ← served by get_skill_script()
│ └── run.py
└── assets/ ← served by get_skill_asset()
└── template.json
```
### Frontmatter schema
Required fields:
| Field | Type | Purpose |
|-------|------|---------|
| `name` | `str` | Human-readable name (not unique; `skill_id` is the directory name) |
| `version` | `str` | Semver-ish; used for cache invalidation |
| `description` | `str` | One-liner shown in `list_skills()` output |
Optional fields:
| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `tags` | `list[str]` | `[]` | Categorical labels; indexed by `search_skills()` |
| `author` | `str` | `""` | Attribution |
| `license` | `str` | `""` | SPDX identifier or custom |
| `min_mcp_version` | `str` | `""` | Semver constraint on the MCP server version |
| `deprecated` | `bool` | `false` | If true, hidden from `list_skills()` by default |
| `replaces` | `str` | `""` | `skill_id` this skill supersedes |
Example `SKILL.md`:
```markdown
---
name: Incident Response
version: 1.2.0
description: Runbooks for on-call incident handling
tags: [ops, sre, on-call]
author: SRE Team
license: Apache-2.0
---
# Incident Response
Follow the runbook for your incident severity...
```
Skills with missing/invalid frontmatter are **skipped at registration time** (logged via the `on_warning` callback, not raised) — one bad skill won't break the catalog.
---
## Tools (10)
### Discovery (NEW in this refinement)
| Tool | Description |
|------|-------------|
| `list_skills(include_deprecated=False, tag=None)` | Return the full catalog. Call this first when you don't know which skill applies. |
| `search_skills(query, include_deprecated=False, limit=10)` | Keyword search across `skill_id`, `name`, `tags`, `description`. Returns ranked matches with relevance scores. |
### Access
| Tool | Description |
|------|-------------|
| `get_skill_metadata(skill_id)` | Frontmatter only — cheap. |
| `get_skill_body(skill_id)` | Full SKILL.md content (frontmatter + markdown). Cached by file mtime. |
| `list_skill_references(skill_id)` | List files in the skill's `references/` directory. |
| `get_skill_reference(skill_id, name)` | Read a specific reference file. |
| `list_skill_scripts(skill_id)` | List files in `scripts/`. |
| `get_skill_script(skill_id, name)` | Read a specific script file. |
| `list_skill_assets(skill_id)` | List files in `assets/`. |
| `get_skill_asset(skill_id, name)` | Read a specific asset file. |
### Typical LLM flow
1. `list_skills()` — see what's available
2. If unclear which fits, `search_skills(user_intent)` — narrow down
3. `get_skill_body(chosen_skill_id)` — load full instructions
4. Follow the skill's instructions, calling `get_skill_reference/script/asset` as needed
---
## Resources (3)
Resources are URI-addressable context that the client decides when to fetch (unlike tools, which the LLM invokes).
| URI | MIME type | Description |
|-----|-----------|-------------|
| `skills://catalog/markdown` | `text/markdown` | Human-readable catalog; one section per skill. |
| `skills://catalog/xml` | `application/xml` | Machine-readable catalog; same fields as `list_skills()`. |
| `skills://tools-usage-instructions` | `text/markdown` | Condensed guidance for the LLM on when to call which tool. |
**Why both tools and resources?** Different MCP clients behave differently. Claude Desktop exposes resources as attachable context; other clients may auto-prefetch. Tools work everywhere. Providing both ensures the discovery layer is reachable regardless of client behavior.
---
## Search scoring algorithm
`search_skills(query)` uses multi-field scoring (no embeddings required for v1):
| Field | Weight | Match strategy |
|-------|--------|----------------|
| `skill_id` | 3.0 | Exact = 3.0, prefix = 1.5, substring = 0.5 |
| `name` | 2.0 | Exact = 2.0, substring = 0.5 |
| `tags` | 2.0 | Exact tag match = 2.0 per tag |
| `description` | 1.0 | Word-level substring = 0.2 per word |
Query is tokenized on whitespace; each token scored independently and summed. Case-insensitive. Results with score 0 are excluded. Ties broken alphabetically by `skill_id`.
---
## Caching
`SkillRegistry` maintains an LRU cache (max 256 entries) keyed by `(skill_id, mtime)`:
- `get_skill_body(skill_id)` first calls `provider.stat()` to get current mtime
- If the cache has an entry for `(skill_id, mtime)`, it's returned without re-reading the file
- If the file changed on disk (different mtime), the cache entry is invalidated and the file is re-read
This means repeated calls to `get_skill_body` are essentially free, and editing a `SKILL.md` on disk is reflected on the next call without server restart.
---
## Security
Path traversal is rejected at the provider layer:
- `skill_id` must not contain `/`, `\`, or be `.`/`..`/empty/hidden
- `name` (within `references/` etc.) must be a single file segment
- Invalid IDs return `None` (no file access attempted)
Example:
```python
>>> get_skill_body("../../etc/passwd")
None
```
---
## Extending
### Adding a new skill
1. Create `./skills/<skill_id>/SKILL.md` with required frontmatter
2. Optionally add `references/`, `scripts/`, `assets/` subdirectories
3. Restart the server (or call `get_skill_body` again — the cache invalidates on mtime change)
### Adding a new provider
Implement the `SkillProvider` ABC (5 methods: `list`, `stat`, `read`, `list_subdir`, `read_subfile`), then pass it to `create_mcp_server(provider=YourProvider())`. The registry and all 10 tools work unchanged.
The `GitSkillProvider` stub in `skill_provider.py` shows the implementation plan for a future git-backed provider (shallow clones, atomic checkout swap, per-skill version pinning via git refs).
---
## Roadmap
- [ ] `GitSkillProvider` — pull skills from a remote git repo (shared team catalogs, version pinning)
- [ ] `search_skills_semantic()` — embeddings-based semantic search (companion to the keyword tool)
- [ ] Pagination for `list_skills()` once catalogs exceed ~500 skills
- [ ] Structured logging integration (replace default `print` warning callback)
- [ ] Integration tests for cache invalidation (modify SKILL.md between calls)
- [ ] CLI tool for skill scaffolding (`new-skill <name>` creates the directory + frontmatter template)
Open questions are tracked in section 7 of [`discovery_tools_spec.md`](./discovery_tools_spec.md).
---
## License
TBD — set in your own project's `LICENSE` file.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues