Tools, MCP & Skills
What goes into Ratel's catalogs: local tools, MCP server tools, and skills, registered once, ranked per turn by search_capabilities, loaded on demand.
Ratel manages three kinds of capabilities: local tools (functions in your code), MCP server tools (from upstream servers), and skills (Markdown playbooks).
They live in two catalogs (ToolCatalog for tools, SkillCatalog for skills) behind one contract; in TypeScript, one ratel() core holds both — r.tools and r.skills. Everything is registered once, ranked per turn by search_capabilities, and loaded on demand. See Architecture for how the pieces fit.
Tools & MCP servers
A local tool is a schema plus a handler you register directly. An upstream MCP server contributes its tools through the same path.
Both land in the same ToolCatalog and rank as one surface: search_capabilities scores them together, and invoke_tool runs whichever wins. The agent never cares where a tool came from.
See Register tools (TypeScript, Python) for the how.
Skills
A skill is a reusable Markdown playbook (a deploy runbook, a debugging checklist) registered with ranked metadata so an agent finds it at the moment it matters. Where a tool is executed, a skill is read, not executed: the body is the payload.
The agent discovers a skill through search_capabilities and pulls the full body into context with get_skill_content, two of the three progressive disclosure tools. Then it follows the instructions.
Skills vs tools
A tool is one operation: the model fills in arguments, the runtime executes. A skill is instructions: a multi-step procedure the agent carries out itself. When the knowledge is "how to do this well" rather than "one operation to run", instructions beat schemas.
ADR-0005 makes two properties load-bearing:
- Read, not executed. Nothing runs when a skill loads.
- Advisory. A task completes without the skill, just worse, so surfacing fails silently and must be measurable. The SDK emits
skill_searchandskill_invoketrace events; see Telemetry.
The advisory property is why there is no standalone skill-search tool. The agent must search for tools anyway, so skills ride in the same search_capabilities response: the tool's necessity carries the skill. ADR-0005 rejected the alternatives: separate skill-search (skippable), one merged ranked list (starvation, incomparable scores), and a related-skills side-channel (a redundant third mechanism).
The skill shape
A skill is { id, name, description, tags, tools, metadata, body }. Only id, name, and description are required; the rest defaults to empty in both SDKs.
| Field | Required | Indexed for ranking | Purpose |
|---|---|---|---|
id | yes | no | lookup key for get_skill_content |
name | yes | yes | ranking signal, matched whole and identifier-split |
description | yes | yes | ranking signal; shown in the skills bucket, compacted to 160 chars |
tags | no, [] | yes | author-declared labels and task phrases ("frontend", "login form") folded into the indexed text |
tools | no, [] | no | ids of tools the playbook calls; pulled into the tools bucket when the skill matches |
metadata | no, {} | no | free-form context for higher layers, e.g. {"stacks": ["react"]}; never matched as query terms |
body | no, "" | no | the Markdown payload returned by get_skill_content |
Worth memorizing: tags are query terms; metadata is carried context, never matched. body is deliberately excluded from the index, so a 15 KB playbook never skews relevance.
A separate corpus, an independent budget
SkillCatalog is the on-demand analog of ToolCatalog: register once, search ranks by relevance, the body is fetched only on load. Its separate corpus ranks name, description, and tags: BM25 by default, semantic or hybrid when enabled.
search_capabilities returns two independently ranked buckets, tools and skills, each with its own top-K budget (topKSkills defaults to 3, topKTools to 5, each capped at 50). Scores are never compared across the two corpora, so a relevant skill can never be crowded out by a pile of matching tools.
Declared tools ride in
When a skill matches, search_capabilities also pulls its declared tools into the tools bucket, playbook and toolkit in one turn. The mechanics:
- Additive: pulled-in tools do not count against
topKTools. - Score 0: a query-matched tool keeps its score; a skill-declared dependency rides in at 0.
- Deduped: a tool that matched both ways appears once.
- Must exist in the
ToolCatalog: unknown declared ids are silently skipped.
Wiring
In TypeScript, ratel() wires this for you: register on r.skills, and the search_capabilities and get_skill_content from r.modelTools() share the core's skill catalog — the skills bucket is always advertised, regardless of registration order.
Assembling the funnel from the factories instead (the only path in Python)? Pass the SkillCatalog as the second argument to the search factory, and hand the agent get_skill_content built from the same catalog. Without it, the skills bucket is always [].
On that path, register skills before building the search tool: by default its description advertises the skills bucket and get_skill_content only when the catalog is non-empty at construction time. In TypeScript (0.5.1+), SearchCapabilitiesOptions.advertiseSkills pins the clause on or off explicitly — ratel().modelTools() uses it so the exposed set never depends on registration order.
import asyncio
from ratel_ai import Skill, SkillCatalog, ToolCatalog, get_skill_content_tool, search_capabilities_tool
catalog = ToolCatalog() # ...register tools...
skills = SkillCatalog()
asyncio.run(skills.register(
Skill(
id="vercel-deploy",
name="vercel-deploy",
description="How to deploy to Vercel: env vars, preview vs production, rollbacks.",
tags=["deploy", "ship to production"], # indexed: query terms
tools=["vercel__deploy", "fs__read_file"], # dependency edge, not indexed
metadata={"stacks": ["next", "vercel"]}, # carried context, never matched
body="## Deploying to Vercel\n1. ...",
)
))
search = search_capabilities_tool(catalog, skills) # 2nd argument: skills bucket is live
load = get_skill_content_tool(skills) # id: "get_skill_content"import { ratel } from "@ratel-ai/sdk";
const r = ratel(); // ...register tools on r.tools...
await r.skills.register({
id: "vercel-deploy",
name: "vercel-deploy",
description: "How to deploy to Vercel: env vars, preview vs production, rollbacks.",
tags: ["deploy", "ship to production"], // indexed: query terms
tools: ["vercel__deploy", "fs__read_file"], // dependency edge, not indexed
metadata: { stacks: ["next", "vercel"] }, // carried context, never matched
body: "## Deploying to Vercel\n1. ...",
});
// Both tools share the core's skill catalog: the skills bucket is live.
const { search_capabilities, get_skill_content } = r.modelTools();Ratel Local loads skills from disk and manages them from the CLI. See Managing skills.
How the agent uses a skill
search_capabilities surfaces the skill
The user says "ship this to production" and no deploy tool is in context. The agent searches; the skill ranks in the skills bucket, and its declared tools join the tools bucket at score 0:
{
"tools": {
"groups": [
{ "server": { "name": "vercel" }, "hits": [{ "toolId": "vercel__deploy", "score": 0, "description": "...", "inputSchema": {} }] },
{ "server": { "name": "fs" }, "hits": [{ "toolId": "fs__read_file", "score": 0, "description": "...", "inputSchema": {} }] }
]
},
"skills": [
{ "skillId": "vercel-deploy", "score": 1.42, "description": "How to deploy to Vercel: env vars, preview vs production, rollbacks." }
]
}get_skill_content pulls the body
get_skill_content({ "skillId": "vercel-deploy" }) returns { "body": "## Deploying to Vercel\n1. ..." }. An unknown id returns a structured { error, isError: true } pointing back to search_capabilities, never a crash.
The agent follows the playbook
The full Markdown is now in context. The agent works through the steps, running the tools via invoke_tool. Loading the skill executed nothing; the model does the work.
Next steps
Register tools (TypeScript)
Wire local functions and MCP servers into r.tools.
Register tools (Python)
The same registration flow, idiomatic Python.
Progressive disclosure tools
The full contract of search_capabilities, invoke_tool, and get_skill_content.
Keyword search
How BM25 ranks both catalogs and how to write retrievable definitions.
Architecture
The layers of the Ratel SDK (Rust engine, SDK bindings, catalogs, retrieval, telemetry) and how one agent turn flows through them.
Progressive disclosure tools
The language-neutral contract for the three capability tools: search_capabilities, invoke_tool, and get_skill_content; request and result shapes, the error table, and how to construct them.