LLM-framework-agnostic skill search library.
SaferSkills independently audited skill-search (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
LLM-framework-agnostic skill search library.
Parses SKILL.md files in a skills directory, builds a fast full-text search index using tantivy (Rust-based), and provides tool definitions + execution handlers that work with any LLM library (OpenAI SDK, Anthropic SDK, LangChain, LiteLLM, etc.).
pip install skill-searchfrom skill_search import SkillSearch
# Initialize with skill directories
ss = SkillSearch(skills_dirs=["./skills"])
# Get tool definitions (OpenAI function calling format)
tools = ss.get_tool_definitions()
# Get system prompt with skill listing
system_prompt = ss.get_system_prompt()
# Execute tool calls from LLM
result = ss.call_tool("search_skills", {"query": "API reference", "top_k": 3})import json
from openai import OpenAI
from skill_search import SkillSearch
client = OpenAI()
ss = SkillSearch(skills_dirs=["./skills"])
messages = [
{"role": "system", "content": ss.get_system_prompt()},
{"role": "user", "content": "How do I use the Figma API?"},
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=ss.get_tool_definitions(),
)
# Handle tool calls
for choice in response.choices:
if choice.message.tool_calls:
for tc in choice.message.tool_calls:
result = ss.call_tool(
tc.function.name,
json.loads(tc.function.arguments),
)
messages.append({"role": "tool", "content": result, "tool_call_id": tc.id})from anthropic import Anthropic
from skill_search import SkillSearch
client = Anthropic()
ss = SkillSearch(skills_dirs=["./skills"])
# Convert to Anthropic format
tools = [
{
"name": t["function"]["name"],
"description": t["function"]["description"],
"input_schema": t["function"]["parameters"],
}
for t in ss.get_tool_definitions()
]
response = client.messages.create(
model="claude-sonnet-4-20250514",
system=ss.get_system_prompt(),
messages=[{"role": "user", "content": "How do I search in Jira?"}],
tools=tools,
)
# Handle tool use
for block in response.content:
if block.type == "tool_use":
result = ss.call_tool(block.name, block.input)| Tool | Description |
|---|---|
list_skills | List all available skills |
read_skill | Read full SKILL.md content |
search_skills | Full-text search (BM25 via tantivy) |
read_resource | Read supplementary resource files |
---
name: my-skill
description: Brief description of the skill
---
# My Skill
## Usage
1. Step 1
2. Step 2SKILL.md files and parses YAML frontmattercall_tool() executes LLM tool calls and returns results as stringsLLM tool calls are treated as untrusted input and protected with defense-in-depth.
| Layer | Location | Protection |
|---|---|---|
| Discovery | discover_resources() | Symlinks resolved; paths outside skill directory excluded |
| Input validation | read_resource handler | Resource names containing .. are rejected |
| Path resolution | read_resource handler | Resolved paths verified to be within skill directory |
| Whitelist | read_resource handler | Only pre-discovered resources are accessible |
Only these file extensions are indexed as resources:
.md, .json, .yaml, .yml, .csv, .xml, .txt
Executable files (.py, .sh, .exe, etc.) and binaries are excluded.
uv run pytest tests/ -vMIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.