skill-catalog

Read-only MCP server exposing the repo skill catalog (skills/, docs/public/generated-registries/skills-catalog-index.json)

MCP FastMCP 2 deps

Read-only MCP server exposing the repo skill catalog (skills/, docs/public/generated-registries/skills-catalog-index.json)

FieldValue
Namemcp-skill-catalog
Version0.1.0
Python>=3.13
mcp/skill-catalog/server.py
"""MCP server: skill-catalog.
Read-only access to the repo skill catalog: frontmatter-derived catalog
nodes (`wagents.catalog.collect_nodes`, kind == "skill") and the generated
public machine SSOT (`docs/public/generated-registries/skills-catalog-index.json`).
No tool in this server mutates the repository.
"""
from __future__ import annotations
from typing import Any
from fastmcp import FastMCP
from wagents.mcp_shared.catalog_readers import catalog_summary, find_node, load_skills_catalog_index, node_to_dict
mcp = FastMCP("Skill Catalog")
_READ_ONLY = {
"readOnlyHint": True,
"destructiveHint": False,
"idempotentHint": True,
"openWorldHint": False,
}
@mcp.tool(annotations=_READ_ONLY)
def list_skills(query: str = "") -> list[dict[str, Any]]:
"""List repo skills from frontmatter-derived catalog nodes.
Use this to browse or search the skill catalog by name/description/tags.
Pass *query* (case-insensitive substring) to filter by id, title, or
description; leave empty to list every skill. Returns lightweight
summaries (id, title, description, source_path) — use `get_skill` for
full frontmatter metadata on a specific skill.
"""
from wagents.catalog import collect_nodes
needle = query.strip().lower()
results = []
for node in collect_nodes():
if node.kind != "skill":
continue
haystack = f"{node.id} {node.title} {node.description}".lower()
if needle and needle not in haystack:
continue
results.append({
"id": node.id,
"title": node.title,
"description": node.description,
"source_path": node.source_path,
})
return sorted(results, key=lambda r: r["id"])
@mcp.tool(annotations=_READ_ONLY)
def get_skill(skill_id: str) -> dict[str, Any]:
"""Return full catalog metadata (frontmatter, body, source path) for one skill.
*skill_id* is the kebab-case skill name matching its directory under
`skills/<skill_id>/SKILL.md`. Raises if the skill is not found in the
catalog — call `list_skills` first if the exact id is unknown.
"""
node = find_node("skill", skill_id)
if node is None:
raise ValueError(f"No skill found with id={skill_id!r}. Call list_skills() to browse available ids.")
return node_to_dict(node)
@mcp.tool(annotations=_READ_ONLY)
def skills_catalog_index() -> dict[str, Any]:
"""Return the generated public skills-catalog-index.json payload.
This is the machine SSOT for the public skills catalog (custom + curated
external), including trust tiers, install commands, and target agents —
a superset of frontmatter-only data available from `list_skills`.
Regenerated by `wagents docs generate`; returns an empty index shape
({"version": 0, "skills": []}) if it has not been generated yet.
"""
return load_skills_catalog_index()
@mcp.tool(annotations=_READ_ONLY)
def skill_catalog_summary() -> dict[str, int]:
"""Return coarse counts of catalog nodes by kind (skills, agents, mcp).
Useful as a cheap sanity check before running broader catalog queries.
"""
return catalog_summary()
if __name__ == "__main__":
mcp.run()

View source on GitHub