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.
Three capability tools give the model self-service access to the whole catalog: search_capabilities finds by intent, invoke_tool runs a hit by id, get_skill_content loads a skill's playbook. This page is their contract.
Their descriptions and JSON schemas are a product contract shown to the model, kept verbatim across the Python and TypeScript SDKs. Ratel Local registers these same objects as its MCP tools. Parameter and result keys are camelCase in both languages (toolId, topKTools, inputSchema) — the wire contract is language-neutral.
search_capabilities
One natural-language query returns two independently-ranked buckets: tools (executable, run one via invoke_tool) and skills (reusable playbooks, load one via get_skill_content). Each bucket has its own result budget and corpus, ranked with that catalog's selected method — BM25 by default, semantic and hybrid opt-in. A relevant skill is never crowded out by matching tools, and scores are never compared across the two.
| Parameter | Type | Required | Behavior |
|---|---|---|---|
query | string | yes | describe what you want to do |
topKTools | integer | no | max tools to return, default 5 |
topKSkills | integer | no | max skills to return, default 3 |
Top-K values must be positive integers, capped at 50. Anything else (missing, 0, negative, fractional) falls back to the default — a stray value can never return zero results or an unbounded set.
{
"tools": {
"groups": [
{
// grouped by server: the toolId prefix before the first "__"
"server": { "name": "fs", "description": "filesystem helpers" },
"hits": [
{
"toolId": "fs__read_file",
"score": 1.87, // BM25 score of the query match (illustrative)
"description": "Read a file from local disk.",
"inputSchema": { "properties": { "path": { "type": "string", "description": "path to read" } } }
}
]
}
]
},
"skills": [
{
"skillId": "vercel-deploy",
"score": 1.42,
"description": "How to deploy to Vercel: env vars, preview vs production, rollbacks."
}
]
}- Both keys are always present: no skill catalog wired means
skillsis[]; no tool matches meanstools.groupsis[]. - Hits are grouped by server, in first-hit order. A hit's server is its
toolIdprefix before the first"__"; an id with no separator is its own group. - Each hit carries exactly
toolId,score,description(the raw registered description), andinputSchema. Skill hit descriptions are compacted: whitespace collapsed, clipped to 160 characters on a word boundary. - A matched skill's declared
toolsare pulled into the tools bucket atscore: 0— additive beyondtopKTools, deduped against query hits — so the agent gets a playbook and its toolkit in one turn. See Tools, MCP & Skills.
invoke_tool
Runs a catalog tool by id: catalog.invoke(toolId, args) under the hood.
| Parameter | Type | Required | Behavior |
|---|---|---|---|
toolId | string | yes | id of the tool to invoke |
args | object | yes | the tool's arguments, matching its inputSchema, nested as a single object |
The nested-args contract: arguments go under args, never flattened to the top level. The runtime forgives the common model mistake — when args is missing or null, the remaining top-level keys (minus toolId) become the arguments — though a host that validates the input schema first may reject the flattened form. A non-null args that is not an object is a structured error; nothing is forwarded.
On success you get the tool's raw result back, untouched, no envelope:
// invoke_tool({ "toolId": "fs__read_file", "args": { "path": "/tmp/x" } })
{ "contents": "contents of /tmp/x" }invoke_tool never throws to the caller. Every failure returns { "error": "...", "isError": true }, so a model mistake stays recoverable inside the loop instead of crashing the host:
| Failure | Returned error |
|---|---|
Unknown or missing toolId | unknown toolId: <id>. Use the catalog's search tool to discover available ids. |
Non-object args | invalid args for <id>: `args` must be an object containing the tool's arguments. |
Tool throws UnauthorizedError | needs_auth, always with a re-auth hint; when the upstream server is inferable from the id prefix, an upstream key names it and the hint appends its name |
| Tool throws anything else | tool <id> threw: <message> |
The factory takes an optional callback (onUnauthorized in TypeScript, on_unauthorized in Python, sync or async) invoked with the upstream server name on the needs_auth path.
get_skill_content
The read counterpart to invoke_tool: skills are read, not executed. The flow: search_capabilities surfaces a skill in the skills bucket, get_skill_content pulls its complete playbook into context, the agent follows it. Bundled scripts or files are referenced by absolute path inside the body.
| Parameter | Type | Required | Behavior |
|---|---|---|---|
skillId | string | yes | id of the skill to load |
// get_skill_content({ "skillId": "vercel-deploy" })
{ "body": "## Deploying to Vercel\n1. ..." } // the full SKILL.md Markdown
// unknown or missing skillId: a structured error, never a throw
{ "error": "unknown skillId: <skillId>. Use search_capabilities to discover available ids.", "isError": true }The output schema declares body, error, and isError all optional, deliberately. Both shapes must validate as structured content; requiring body would turn the error branch into a protocol error instead of the declared structured error.
Constructing the capability tools
Each factory wraps your catalog and returns a plain ExecutableTool whose id equals its name, ready to hand to your agent loop:
from ratel_ai import ToolCatalog, SkillCatalog, search_capabilities_tool, invoke_tool_tool, get_skill_content_tool
catalog = ToolCatalog()
skills = SkillCatalog()
# ...register tools and skills...
search = search_capabilities_tool(catalog, skills) # id: "search_capabilities"
invoke = invoke_tool_tool(catalog) # id: "invoke_tool"
load = get_skill_content_tool(skills) # id: "get_skill_content"import { ToolCatalog, SkillCatalog, searchCapabilitiesTool, invokeToolTool, getSkillContentTool } from "@ratel-ai/sdk";
const catalog = new ToolCatalog();
const skills = new SkillCatalog();
// ...register tools and skills...
const search = searchCapabilitiesTool(catalog, skills); // id: "search_capabilities"
const invoke = invokeToolTool(catalog); // id: "invoke_tool"
const load = getSkillContentTool(skills); // id: "get_skill_content"The skill catalog is optional. Pass one with skills registered and the tool's description advertises the skills bucket to the model; without one, skills is always []. Gating is checked at construction time and an empty catalog counts as no skills — build the tool after registering.
The search factory also accepts upstream server info (upstreamServers / upstream_servers), listed in the description so the model knows what the catalog aggregates.
Every search the capability tools run is tagged origin: "agent" in trace events and OTel spans, so agent-initiated discovery stays distinguishable from host-code calls to catalog.search. See Telemetry.
Upgrading from 0.1.x?
search_tools (factories searchToolsTool / search_tools_tool) is the pre-0.2.0 search, still exported as a deprecated compatibility shim with its original behavior: a tools-only { "groups": [...] } result and a single topK input. It is not an alias of the two-bucket tool. Migrate to search_capabilities; registering both serves the old and new names during a migration window.
Next steps
Optimize agent capabilities
The use-case view: put everything in a catalog and disclose progressively, plus what it saves.
Retrieval and search
How the buckets are ranked: BM25 keyword search by default, semantic and hybrid when you opt in.
Use the discovery tools (TypeScript)
Hand these tools to your agent loop and reach the whole catalog from inside a turn.
Use the discovery tools (Python)
The same self-service discovery flow, idiomatic Python.
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.
Retrieval and search
How Ratel ranks the catalog: BM25 keyword search over a schema-aware projection by default, semantic and hybrid ranking when you opt in, and how to choose between them.