Tool Catalog
A deployed assistant can do more than generate text. Divinci’s tool surface spans three independent subsystems, each with its own config and its own gate, and all three are off by default:
| Subsystem | What it does | Configured via | Gate |
|---|---|---|---|
| Skill/tool loop (RP1) | In-chat tools the model calls during a send (search, fetch, RAG, email, SMS, calendar) | Release.skillConfig | skillConfig.enabled |
| MCP tools | The platform’s full surface exposed to external MCP/agent clients | Release.mcpConfig | mcpConfig.enabled |
| Site Control | Navigate / submit forms on the embedded customer site | Release.siteControl | siteControl.enabled (THE WALL) |
Skill/tool loop (in-chat tools)
Section titled “Skill/tool loop (in-chat tools)”What it is. When a chat turn shows tool intent, the send path hands the
message to a tool-calling model that loops call-model → execute-tool → feed-result until the model answers tool-free or a safety net trips. Use it when
you want an in-chat assistant that actually does things — searches the web,
fetches a URL, retrieves from RAG, emails or texts the person it is talking to,
or books a calendar appointment.
Tools reach the loop from three places, all merged per turn:
- Catalog skills — Divinci-authored integrations, attached to the workspace as
a
WhitelabelSkillinstance and then selected on the release. - MCP skills — external MCP servers you connect by OAuth or a static API key; each server’s tools become individually selectable.
- User skills — instances owned by the person chatting, loaded from their own account at turn time rather than from the release.
The catalog
Section titled “The catalog”Seven integrations are registered today. mutates is the property that decides
whether the loop stops for confirmation before running an action.
| Skill id | Actions | Mutates | Notes |
|---|---|---|---|
web-search | webSearch | ❌ | |
scrape-url | fetchUrl | ❌ | |
rag-search | ragSearch | ❌ | Backed by the release’s resolved RAG vector |
google-calendar | getAvailabilities | ❌ | |
bookAppointment, cancelAppointment | ✅ | Confirmation required | |
email-release | sendEmail | ✅ | Self-directed — may only reach the verified chatting user |
email-user | sendEmail | ✅ | User-owned only. May address the whole workspace org |
sms-release | sendSms | ✅ | Self-directed |
Image, video and diagram generation exist in the tree but are not registered in the catalog — each still needs owner-facing provider/key configuration before it can be enabled. Treat them as unavailable rather than opt-in.
Enabling it
Section titled “Enabling it”One gate: skillConfig.enabled. There is no server env var any more.
await client.releases.updateInWorkspace(workspaceId, releaseId, { skillConfig: { enabled: true, maxToolIterations: 5, // keyed by WhitelabelSkill INSTANCE id — create the instance first catalogSkills: { "6a67afca5599956295482dfc": ["sendEmail"] }, mcpSkills: { "6a72d8ae5c76b78e24589e85": [] }, // [] = all of that server's tools toolCallingAssistant: { id: "@cf/moonshotai/kimi-k2.7-code" }, },});Or from the CLI:
divinci release update <releaseId> --skills-enabled \ --catalog-skills '{"6a67afca5599956295482dfc":["sendEmail"]}'skillConfig fields and defaults
Section titled “skillConfig fields and defaults”| Field | Type | Default | Notes |
|---|---|---|---|
enabled | boolean | false | The only gate. False = the loop never runs, whatever is attached |
catalogSkills | Record<instanceId, string[]> | {} | [] as the value = all of that skill’s actions |
mcpSkills | Record<serverId, string[]> | {} | [] as the value = all of that server’s active tools |
toolCallingAssistant | { id } | the release’s own assistant | Must implement native tool calling |
fallbackToolCallingAssistants | { id }[] | — | Ordered retry chain; each must also be tool-capable |
maxToolIterations | number ≥ 1 | 5 | Tool-call rounds allowed in one turn |
businessDisplayName | string | — | Surfaced to the model as who its skills act for |
skillConfig merges shallowly over the stored value, so sending only
catalogSkills leaves enabled and toolCallingAssistant intact.
Confirmation, and why it can’t be faked
Section titled “Confirmation, and why it can’t be faked”Any action that changes external state carries mutates: true, and the loop
stops before executing it to ask the chat user. A confirmation is only accepted
as an HMAC-signed token the server itself issued for that transcript, so a
client cannot fabricate one — this closes a confirmation-bypass hole that a
plain “confirmed: true” flag would leave open.
The one documented exception is selfDirected: an action whose target is
resolved server-side from the current user’s verified identity and can never be
model-chosen. Its blast radius is the person who asked, so it does not force a
confirmation stop — which matters on surfaces that have no way to confirm, a
phone call being the obvious one, where the loop would otherwise stall at
pending-confirmation and the action would silently never run. mutates stays
true, so an owner can still opt back into confirmation per instance.
Safety nets
Section titled “Safety nets”| Bound | Value |
|---|---|
| Tool-call rounds per turn | maxToolIterations, default 5 |
| Per-tool-result size | 4000 chars |
| Confirmation for mutating tools | Required unless selfDirected |
| Tool-intent signal | Always recomputed server-side |
Gotchas
Section titled “Gotchas”- Instance ids, not catalog ids.
catalogSkillsis keyed by theWhitelabelSkillinstance id you get after attaching a skill to the workspace — not by the catalog id (web-search,email-release, …). The CLI rejects a value that looks like a catalog id; the API rejects an instance that is not ready or belongs to another workspace. Creating an instance and connecting it is covered in Provider Keys (BYOK) & Skills. - Enabling the loop without a tool-capable model is rejected. If you don’t
name a
toolCallingAssistant, it defaults to the release’s own assistant, which must then support native tool calling or the save fails. - The loop never fails a message. Any error inside it — or a turn that simply doesn’t route — falls through to normal generation. A broken skill degrades the answer; it does not 500 the chat.
- The client’s tool-intent signal is never trusted. It is always recomputed server-side, so a crafted request cannot force routing or name a tool.
- A handoff outranks the loop. Routing is sequenced: cross-release handoff first, then the tool loop, then normal generation. A turn handed to another release makes its own tool decisions and does not carry this one’s.
- Enabling the loop is what makes a skill real. Attaching skill instances
while
enabledis false leaves the assistant able to describe actions it cannot perform — the failure mode is a confident claim, not an error.
MCP tools
Section titled “MCP tools”What it is. The MCP server (workers/mcp-server) exposes ~60+ named tools
across ~25 categories — chat, RAG, config, fine-tune, QA, analytics, terms, trust,
and more — to external MCP/agent clients. Use mcpConfig when you want to expose
some of that surface to an MCP client rather than to the in-chat model.
Enabling it
Section titled “Enabling it”# POST, not PATCH — there is no PATCH route on releases.# /release/$RELEASE updates a DRAFT; a published release uses .../updatecurl -X POST https://api.stage.divinci.app/white-label/$WL/release/$RELEASE \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"mcpConfig":{ "enabled":true, "exposedTools":["send_message","search_knowledge","get_transcript"], "allowAnonymousMcp":true, "maxSpendPerTokenCents":500, "mcpRateLimit":{"requestsPerMinute":30,"requestsPerDay":2000} }}'mcpConfig fields and defaults
Section titled “mcpConfig fields and defaults”| Field | Type | Default | Notes |
|---|---|---|---|
enabled | boolean | false | The MCP gate |
exposedTools | string[] | empty = all release tools | Unknown names are rejected by the API |
allowedScopes | string[] | — | Restricts callable scopes |
mcpRateLimit.requestsPerMinute | number | 60 | Per-minute cap |
mcpRateLimit.requestsPerDay | number | 10000 | Per-day cap |
allowAnonymousMcp | boolean | false | Requires a positive maxSpendPerTokenCents |
maxSpendPerTokenCents | cents | 1000 ($10) | Per-token spend cap |
Gotchas
Section titled “Gotchas”- Unknown
exposedToolsare rejected. Listing a tool name the server doesn’t know fails validation (it would otherwise expose nothing). - Anonymous access needs a spend cap.
allowAnonymousMcp: truerequires a positivemaxSpendPerTokenCents.
Site Control (navigation / action tools)
Section titled “Site Control (navigation / action tools)”What it is. The crawler builds a SiteManifest of the embedded customer site;
Site Control turns that map into executable tools — a navigate_site action plus
per-form submit actions. Use it when you want the assistant to move around the
embedded site (and, opt-in, fill and submit forms with runtime confirmation).
Enabling it
Section titled “Enabling it”# Via the SDK:# await client.releases.updateInWorkspace(wl, release, {# siteControl: { enabled: true, allowedCategories: ["navigation"] },# });# Or over the API — note POST, not PATCH:curl -X POST https://api.stage.divinci.app/white-label/$WL/release/$RELEASE \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"siteControl":{"enabled":true,"allowedCategories":["navigation"]}}'
# Add form submission (each submit action is confirmation-gated, always):# -d '{"siteControl":{"enabled":true,"allowedCategories":["navigation","mutation"]}}'siteControl fields and defaults
Section titled “siteControl fields and defaults”| Field | Type | Default | Notes |
|---|---|---|---|
enabled | boolean | false | THE WALL gate |
allowedCategories | "navigation" | "query" | "mutation" | "utility"[] | ["navigation"] | Which action categories are derived |
allowList | string[] | — | Strictly validated |
blockList | string[] | — | Strictly validated |
Manifest-derived limits: navigation route enum capped at 60, max 20 form actions, action name length 48.
Gotchas
Section titled “Gotchas”- Form submissions always confirm. Site Control
mutation/ submit actions carryrequiresConfirmation: true, which config cannot disable. - Sensitive fields are stripped. Password / hidden inputs and any name matching
pass/pwd/card/cvv/ssn/secret/token/otp/pin/account-number/routingnever become fillable params. - Cross-origin hrefs are dropped from navigation actions.
Inspecting derived actions
Section titled “Inspecting derived actions”import { deriveReleaseSiteActions } from "@divinci-ai/models";
// Site Control: derived actions ([] unless siteControl.enabled)const siteActions = deriveReleaseSiteActions(release);For the skill loop there is no equivalent pure selector — which tools a release
advertises is resolved per turn, because it merges the release’s catalogSkills
and mcpSkills with the chatting user’s own skill instances and with live
MCP tool discovery. Read skillConfig off the release to see what the release
contributes; the rest only exists in the context of a specific turn and user.