Tools
A tool is a single capability an agent can call mid-run. The runtime ships a large built-in registry (decorator-registered Python functions) plus any custom tools you register through this namespace. Custom tools are DB-backed rows with kind ∈ stub, webhook (per the runtime's create_tool validator) — there is no mcp/http/bash/python kind enum, and there is no inputSchema/outputSchema/displayName/httpUrl field anywhere on the real object.
For the runtime tool contract, see API → Tools.
Auth. Every route in this namespace requires the control plane's
x-catentio-sessionheader, which this SDK never sends. NeitherapiKeymode nor Bearer-session mode authenticates here. See SDKs → Auth: what actually works.
Namespace
catentio.tools — the CRUD surface this SDK wraps:
catentio.tools.list()
catentio.tools.get(slug)
catentio.tools.create(input)
catentio.tools.update(slug, patch)
catentio.tools.delete(slug)
The control plane also has POST /v1/tools/api-test, POST /v1/tools/{slug}/install (+ installs/approve/reject), and POST /v1/tools/{slug}/run — none of them are wrapped here.
Methods
tools.list
Signature. catentio.tools.list(token?): Promise<{ data: ToolListRow[] }>
import { CatentioSaasClient } from '@forjio/catentio-saas-node';
const catentio = new CatentioSaasClient({ session });
const { data: tools } = await catentio.tools.list();
const customs = tools.filter(t => !t.is_builtin);
console.log(`${customs.length} custom tools registered`);
Real list-row shape:
interface ToolListRow {
slug: string;
description: string;
module: string; // '(db)' for custom tools
category: string; // derived from the module path, or 'custom'
sensitive: boolean;
param_names: string[];
param_count: number;
is_builtin: boolean; // snake_case, not 'isBuiltin'
// present only when is_builtin === false:
kind?: string;
shape?: string;
access?: string;
}
There is no displayName, inputSchema, outputSchema, mcpUrl/httpUrl/bashScript/pythonCode, createdAt, or updatedAt field on this row — none of those exist.
tools.get
Signature. catentio.tools.get(slug, token?): Promise<ToolDetail>
const tool = await catentio.tools.get('discord-send-message');
console.log(tool.params_schema); // JSON Schema — not 'inputSchema'
Real detail shape (built-in):
interface BuiltinToolDetail {
id: number | null; // the tool's doc-twin graph node id, if it has one
slug: string;
description: string;
module: string;
category: string;
sensitive: boolean;
params_schema: object;
recent_invocations: Array<{ id: number; ts: string; run_id: string; agent: string | null; transport: string | null; params_preview: unknown }>;
is_builtin: true;
}
Custom tools add: kind ('stub' | 'webhook'), shape, access, body (kind-specific — e.g. url/method for webhook), integration, schema, version, and is_builtin: false.
tools.create
Signature. catentio.tools.create(input, token?): Promise<ToolDetail>
Real fields: slug (required, ≤64 chars), description?, kind? ('stub' or 'webhook', default 'stub'), schema? (JSON Schema object), body? (kind-specific object). There is no displayName, mcpUrl, httpUrl/httpMethod/httpHeaders, bashScript, or pythonCode field — none of those exist. Built-in tools cannot be created here. Creation is subject to a custom_tools tier cap; a duplicate slug gets 409.
const tool = await catentio.tools.create({
slug: 'jira-find-ticket',
description: 'Look up a Jira ticket by key.',
kind: 'webhook',
body: { url: 'https://acme.atlassian.net/rest/api/3/issue/{key}', method: 'GET' },
schema: {
type: 'object',
properties: { key: { type: 'string' } },
required: ['key'],
},
});
tools.update
Signature. catentio.tools.update(slug, patch, token?): Promise<ToolDetail>
Built-in tools reject
updatewith400("builtin tools are decorator-registered and cannot be edited via the portal"), not409 builtin_tool.
await catentio.tools.update('jira-find-ticket', {
description: 'Look up a Jira ticket by key, includes worklog summary.',
});
tools.delete
Signature. catentio.tools.delete(slug, token?): Promise<void>
Built-in tools reject
deletewith400("builtin tools cannot be deleted via the portal"), not409 builtin_tool.
await catentio.tools.delete('jira-find-ticket');
Common patterns
Audit custom vs built-in
const { data: tools } = await catentio.tools.list();
const custom = tools.filter(t => !t.is_builtin);
console.log(custom.map(t => `${t.slug} (${t.kind})`));
Dynamic input validation
import Ajv from 'ajv';
const ajv = new Ajv();
const tool = await catentio.tools.get('jira-find-ticket');
const validate = ajv.compile(tool.params_schema);
if (!validate({ key: 'PAW-123' })) {
console.error(validate.errors);
}
Errors
The real error body is FastAPI's {"detail": ...}. Because the wire response never has an error key, the SDK doesn't parse out a structured code/message — you get CatentioError with a generic HTTP_ERROR code and the HTTP reason phrase as the message.
| Status | detail |
Cause |
|---|---|---|
| 401 | — | Missing/invalid session — this SDK never sends x-catentio-session. |
| 400 | "slug required (1..64 chars)" etc. |
Bad create() input. |
| 400 | "builtin tools are decorator-registered and cannot be edited via the portal" |
update() on a built-in tool. |
| 400 | "builtin tools cannot be deleted via the portal" |
delete() on a built-in tool. |
| 403 | {"code": "plan_limit_exceeded", ...} |
create() past the custom_tools tier cap. |
| 404 | "unknown tool: {slug}" |
update()/delete() on a slug that doesn't exist. |
| 409 | "tool '{slug}' already exists" |
create() with a slug collision. |
Next
- Skills — versioned bundles of related tools.
- Agents — the principal that calls tools.
- API → Tools — HTTP-level reference.