Tool sources
A tool source is a platform-registered provider of external tools. It extends the agent toolbox beyond the built-in csuite toolset while keeping the broker as the identity boundary: the third-party credential (a Jira PAT, an API key, a remote MCP server’s token) lives only on the broker, KEK-encrypted at rest and write-only over the wire. The only credential on a member machine remains the member’s own bearer token — an agent proves who it is to the broker, and the broker does the rest.
Two kinds:
custom— tools defined declaratively on the platform (name, description, JSON input schema, HTTP binding). The broker executes each invocation against the third-party API itself, injecting the stored credential. Use this to wrap a REST API — “Jira via a service-account PAT” — as a handful of purpose-built tools.mcp— a remote MCP server (Streamable HTTP) the broker connects to as an MCP client with a stored static credential. Upstream tools are discovered, cached, re-exposed to members, andtools/callis relayed. Use this when the provider ships a proper MCP server and you want the broker to hold its identity.
How tools reach an agent
admin ──REST/CLI──▶ registry (sources + tools + bindings + creds)
│ resolved per member
▼
agent ◀─ tools/list ── runner ◀── briefing.toolSources ── broker
agent ── tools/call(jira__create_issue) ─▶ runner
─▶ POST /tool-sources/jira/tools/create_issue/invoke
│ 403 unless bound · audit tool_action
┌──────┴──────┐
kind=custom kind=mcp
fetch + cred MCP client relay + cred
- The briefing carries the member’s resolved tools — every
enabled source the member is bound to (or that is open to
allMembers), with each tool’s name, description, and input schema. - The runner merges them into the agent’s MCP toolbox, namespaced
<source>__<name>(source slugs contain no underscores, so the first__is unambiguous). To the agent they are ordinary tools. - A call dispatches back to the broker’s invoke endpoint on the
member’s own bearer token. The broker independently enforces the
binding (403), executes or relays, appends a
tool_actionto the member’s activity stream, and returns an MCP-shapedCallToolResultthe runner passes through verbatim.
Tool-level failures (upstream 4xx/5xx, timeouts, template errors)
come back as isError: true results the model can read and
self-correct on — MCP convention. Authorization and registry
problems are HTTP errors (403 unbound, 404 unknown, 409 disabled).
Live distribution — the capability channel
Registry changes fan out as data.kind = 'tool_source' channel
events to the source’s visible members plus every tools.manage
holder. A member’s runner reacts by refetching the briefing and —
only when its resolved set actually changed — emitting
notifications/tools/list_changed so the agent re-lists tools
mid-session, no restart.
This is the one event class that earns a prompt-prefix cache break. The doctrine holds: the tool list is a capability surface (changes rarely, on deliberate admin action), message traffic is the state surface, and descriptions never mutate for freshness.
Visibility and bindings
visible = enabled && (allMembers || bound). Bindings are explicit
per-member grants (csuite tools bind jira scout); --all-members
opts the whole team in, including future members. There is no
tools.manage bypass on invoke — admins bind themselves like
anyone else, so there is exactly one access rule.
Credentials
One static credential per source in v1 (OAuth 2.1 consent flows are a planned follow-up):
bearer— sent asAuthorization: Bearer <secret>header— sent as<Name>: <secret>(e.g.X-Api-Key)
Set via PUT /tool-sources/:slug/credential or csuite tools cred.
Write-only: no endpoint ever returns the secret; list/detail
responses expose only hasCredential: true. At rest it is
KEK-encrypted (enc-v1: envelope, same scheme as TOTP secrets) and
credential writes fail closed when no KEK is active.
Least-privilege the credential at its source — the scoping of the
PAT is the control, per the
trust posture.
Custom tool bindings
A custom tool is data, not code:
{
"description": "Fetch a Jira issue by key.",
"inputSchema": {
"type": "object",
"properties": { "key": { "type": "string" } },
"required": ["key"]
},
"binding": {
"method": "GET",
"urlTemplate": "https://your-org.atlassian.net/rest/api/3/issue/{{args.key}}",
"resultPath": "fields.summary",
"timeoutMs": 15000
}
}
Binding fields: method, urlTemplate, optional headers
(values templatable), optional bodyTemplate (a string for raw
text, or a JSON value used as a structural template), optional
contentType, optional resultPath (dot-path extracted from JSON
responses), optional timeoutMs (clamped 1–120s, default 30s).
Template rules ({{args.<name>}}, top-level args only):
- URL — substitutions are URL-encoded. The origin is static: placeholders may appear only in path/query, enforced at save time and re-checked on every execution. Agent-supplied args can never steer a credentialed request to another host.
- Headers — expanded values are rejected if they contain
control characters (header-injection guard).
Authorizationand the credential’s header can never be templated. - JSON bodies — a string value that is exactly one placeholder passes the arg’s raw JSON value through (numbers, arrays, objects). A missing arg in that position omits the containing key, which is how optional API parameters are expressed. Embedded placeholders interpolate scalars and error when missing.
Responses are stream-read with a 64 KiB cap (a marker is appended
when truncated), JSON is parsed for resultPath extraction, and
binary bodies are summarized rather than relayed.
Note: args are not validated against inputSchema broker-side
in v1 — the schema guides the model; missing-placeholder checks are
the execution guard. Cross-origin redirects follow but drop the
Authorization header (undici default) — by design.
MCP sources
Register with the upstream URL, set the credential, then discover:
csuite tools add up --kind mcp --url https://mcp.example.com/v1
csuite tools cred up --bearer <token>
csuite tools refresh up
csuite tools bind up scout
Discovery is explicit — refresh (or an enable transition)
lists the upstream’s tools into a cache; there is no polling. A
tool added upstream 404s on invoke until an operator refreshes (the
error says so). The broker keeps one lazy connection per source,
shared across members, with a 5-minute idle TTL; config or
credential changes drop it so the next call reconnects fresh.
Upstream tool errors relay as isError results; an unreachable
upstream (after one reconnect retry) is a 502 — an admin problem,
not something the agent can fix. Relayed text blocks are capped at
64 KiB each, 256 KiB total.
Audit
Every invocation appends a tool_action event to the calling
member’s activity stream broker-side — tool name
(<source>__<name>), capped input, error flag, duration. This is
authoritative in a way agent-side capture isn’t: it cannot be
evaded by the agent process. View it wherever activity streams
surface (see activity & traces).
Administration
Gated on the tools.manage leaf (see
permissions). Four surfaces: the REST
API (/tool-sources*), csuite tools (see
CLI reference), the web UI (Tools in the
nav), and — for members holding the leaf — the tool_sources_* MCP
tools in the agent toolbox itself (see
MCP tools). Slugs are immutable in
v1 — the event thread key tool:<slug> rides on them;
displayName is the mutable label.
Agent-authored connectors
The MCP admin surface makes connector authorship an agent task: an
admin agent reads an API’s documentation, registers the source,
writes the tool definitions (validation errors name the exact
problem), binds itself, test-calls the tools, and iterates on the
isError results until they work. The common split is AI builds
the connector, human drops the key via the web UI so the secret
never enters agent context. Agents can set credentials too
(tool_sources_set_credential) — the write-only invariant holds
regardless of author — which matters for autonomous rigs that
generate their own keys (a self-provisioned service account, a
minted API token). The tradeoff is that an agent-set secret passes
through the session transcript; treat that as accepted for
self-generated keys and avoidable for human-held ones.
For credentials the agent’s own CLI tooling needs locally (gh, terraform, npm) rather than tools the broker executes, see secrets — broker-held values injected into the agent’s environment at spawn, with the same write-only + KEK posture.
Note for existing teams: stored permission presets don’t
automatically gain new leaves — add tools.manage to your admin
preset (csuite presets set admin --permissions ...). Fresh
installs include it via the wizard.