Give bots tools
Every bot comes with tools to search its own chats, keep memories, and use the library. Give bots more with add-ons, through fxbot.addons: remote MCP servers, whose tools they call, and skills, which they read.
The tools every bot has
| Tool | What it does |
|---|---|
chat_history |
Searches the bot's own messages across all its chats, and reads them in full around a match. |
memory_save, memory_search, memory_forget |
Keep the bot's memories. See Work with memories. |
library_search, library_read, library_save |
Find, open, and save files in the library, only those tagged for the bot. |
skill |
Reads one of the bot's skills. Bots have it once you add a skill. |
Each tool finds its bot from the chat it runs in, never from what the model asks.
Add an MCP server by its address
Look at the server first.
previewconnects without credentials, and answers with its name, how it signs in (none,headers, oroauth), its tools when it lists them without a sign-in, and ahash:import type { AddonPreview } from "@fxbot/sdk"; const ref = { url: "https://mcp.example.com/mcp" }; const page = (await fxbot.addons.preview(ref)) as AddonPreview; console.log(page.name, page.auth, page.tools?.map((t) => t.name));For a server that takes a key, name its header in the ref, such as
{ url, headers: ["Authorization"] }.Add it with that hash. If the server changed since the preview,
addfails with status 409: look at it again.const server = await fxbot.addons.add(ref, page.hash);Give it what it needs to connect. For a key, set the header's value:
await fxbot.addons.setSecrets(server.id, { headers: { Authorization: `Bearer ${key}` } });Values go in once and are sealed. A listed server says only
signedIn: true. For OAuth, see Sign in with OAuth.Allow the tools bots may call. At first, only the server's read-only tools are allowed. Name every tool to allow, by the server's own name for it:
await fxbot.addons.change(server.id, { allowed: ["search_issues", "create_issue"] });addons.list()shows each server'stools, each withreadOnly, itsallowedlist, and itsstatus, such asConnected. A server that signs in lists its tools once it connects.To give the server to some bots only, choose them one by one:
await fxbot.addons.change(server.id, { bots: "each" }); await fxbot.addons.forBot(bot.id, server.id, true);
Bots see each tool under the server's name, such as linear_create_issue.
Add a server from a registry
Search fxbot's list of servers and its copy of GitHub's MCP registry, then add an entry by its name:
import type { AddonSearch } from "@fxbot/sdk";
const { cards } = (await fxbot.addons.search("linear", {})) as AddonSearch;
const ref = { registry: cards[0].name, from: cards[0].from };
const page = (await fxbot.addons.preview(ref)) as AddonPreview;
const server = await fxbot.addons.add(ref, page.hash);Then carry on from step 3 above. A card's from is fxbot for fxbot's list, github for GitHub's registry, or official. { everything: true } adds the official registry's results, for searches of 3 characters or more.
The copy of GitHub's registry fills in when createFxbot starts and every hour after. An app started with syncRegistry: false has nothing to search until a sync has run. It can still add an entry by its name: { registry: "<name>" }.
Sign in with OAuth
For a server whose auth is oauth.
Start the sign-in, and send the person to its
url:const attempt = await fxbot.oauth.start(server.id, origin); console.log(`Open ${attempt.url}`);Finish it the way its
modesays:redirect: the browser comes back to<origin>/api/oauth/callback.originis your page's own, and must be HTTPS. Route that path in your server tofxbot.oauth.callback(url.searchParams).paste: the browser goes to thelocalUrlyou gavecreateFxbot,http://127.0.0.1:8787by default, at/callback. Serve that path withfxbot.oauth.callbacktoo. Where nothing serves it, the page fails to load: pass its address, copied from the browser, tofxbot.oauth.paste(attempt.id, address).
Ask
fxbot.oauth.get(attempt.id)until itsstatusisdoneorfailed. A sign-in expires after 10 minutes.
A server that registers no apps itself needs your own OAuth app: register one with it, then pass it with setSecrets(server.id, { clientId, clientSecret }).
Add a skill
Find it in fxbot's own skills, or on skills.sh for 2 characters or more:
import type { SkillPreview, SkillSearch } from "@fxbot/sdk"; const { cards } = (await fxbot.addons.search("frontend", { kind: "skill" })) as SkillSearch; const ref = { skill: cards[0].id }; // <owner>/<repo>/<skillId>Look at it.
previewreads the skill from its repository's current commit:const page = (await fxbot.addons.preview(ref)) as SkillPreview; if (page.error) throw new Error(page.error); console.log(page.text); // its SKILL.mdAdd it with the hash:
const skill = await fxbot.addons.add(ref, page.hash);
Each bot's instructions now list its skills, and the skill tool reads one when a task needs it. To be sure a bot uses a skill, name it in a message: $name anywhere, or /name at the start, sends the skill along with it:
await fxbot.send(bot.id, { text: "/frontend-design A page for a bakery", messageId: crypto.randomUUID() });profiles.skills(bot.id) lists the names a bot takes. Skills go to all bots, or to chosen ones with change and forBot, as servers do.
Keep add-ons up to date
addons.list() marks an add-on whose source changed. Its mark.kind is update, with the newer version, or deprecated, or removed. Nothing changes by itself. To move one to its newer version, look at it again from the same source, then update it with that hash:
for (const addon of await fxbot.addons.list()) {
if (addon.mark?.kind !== "update") continue;
const ref = addon.kind === "skill" ? { skill: addon.source.skill } : { registry: addon.source!.registry, from: addon.source!.from, allowLocal: addon.allowLocal };
const page = await fxbot.addons.preview(ref);
await fxbot.addons.update(addon.id, page.hash);
}The skills' repositories are checked every hour. addons.check() checks them now.
change(id, { enabled: false }) turns an add-on off, and remove(id) removes it. A change to the tools bots have makes the agents again, after their running turns finish.
If it doesn't work
addorupdatefails with status 409. The server or skill changed since its preview. Look at it again, and use the new hash.previewsays the address is local. A server on this machine or a private network needsallowLocal: truein the ref. It may then usehttp. Use it only for a server you run.- The server's
statusisAdd its header values. Set them withsetSecrets. Header names must be the ones the ref named. - The server's
statusisSign in to use it. Run the OAuth sign-in above. - A tool call fails, saying the tool is not allowed. Add it to
allowedwithchange. - The preview has
local: true, andaddrefuses it. That entry runs as a program on this machine, not as a remote server, and the SDK can't add it yet. - The MCP search finds nothing. The registry's copy hasn't synced: see Add a server from a registry.
fxbot's own page does the same in Add-ons: Add an MCP server and Add a skill.