Skip to main content
Glama
janacibron

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.