Tools
Every tool the server can expose, what its arguments are, and why the list differs between organizations.
The tool list is generated per request, not maintained by hand. Two tools are always present; the rest depend on what your key reaches and what those deployments publish.
| Tool | Present when |
|---|---|
list_agents | Always |
describe_deployment | Always |
<channel>_send_message | One per JSON message door in the manifest of a deployment a reachable agent names |
record_sent_reply | Such a deployment advertises the record capability and runs kit 0.31.0 or later |
| Telemetry tools | The key carries permissions.telemetry |
| Outcome tools | The key carries permissions.manageAgents |
get_agent_models | The key carries permissions.manageAgents |
Start with list_agents. Every other tool takes a core argument that must be
one of the slugs it returns, and that argument may only be omitted when the key
reaches exactly one agent. describe_deployment can take a deployment's name
instead.
An agent is reached through its deployment: the one its
deployment names, at the address
an operator approved for it, under the agent's routing key
(/a/{routingKey}/…). Requests are signed as the deployment's name, which is
what the deployment verifies. An agent that names no deployment is listed, but
it has no conversation tools until it is linked. The MCP server never falls
back to your only deployment, as some agents API routes do.
list_agents
List the Subcore agents this API key can reach, each with its tenant path and the deployment that serves it.
No arguments. Returns each agent's slug, name, description and
tenantPath, and its deployment:
{
"slug": "clinic-one",
"name": "Clinic One",
"description": null,
"tenantPath": "/a/k3x9q2m",
"deployment": { "name": "clinics-production", "url": "https://clinics.example.com", "connectionStatus": "success" }
}deployment is null for an agent that names none. Any agent that cannot be
reached also carries a problem string that says why and what fixes it:
- it names no deployment. Link it on its Configuration → Connection tab in the
console, or with
PUT /agents/{slug}and adeployment; - its deployment's address has not been approved, or is not a public HTTPS address;
- its deployment serves no agents by routing key.
A deployment's former default agent is retired and not listed.
It also reports keyScope (whether the key spans the whole organization or is
limited to named cores) and serverSigning (whether the server can sign
requests to deployments at all).
describe_deployment
A deployment's self-description: the public manifest merged with the signed describe document.
| Argument | Type | Description |
|---|---|---|
deployment | string | A deployment name, as list_agents shows it. Optional when the key reaches exactly one deployment |
core | string | An agent slug: describes the deployment that agent names |
includePrompts | boolean | Include prompt file and compiled bundle bodies. Default false |
Pass deployment or core, never both. An agent may share a deployment's name,
so one string could otherwise name two different targets, and passing both is
refused. An agent that names no deployment is refused with a message pointing at
its Connection tab. A key limited to some agents reaches only the deployments
they name.
Returns the deployment's name, approved URL and connection status, and, when
asked by core, the agent's slug, name and tenant path. Then its channels and
doors, its kit version, model, the tools it wires to each channel (an agent's
own choices are on the
Agent object) and per-door
auth posture, plus prompt files by hash. It also lists which conversation tools were generated
from it, and which doors were excluded and why. That is the fastest way to find
out why a tool you expected is not there.
For a deployment on kit 0.21.3 or later, the signed description also lists the
prompt variables it declares, under description.prompts.variables, and says
which of them it requires. From kit 0.24.0 a deployment refuses an agent that is
missing a required one, so this is where to read the list before you create an
agent. See
variables a deployment requires.
From kit 0.28.2, the entry for a variable the deployment's code reads, such as
an agent's voice, carries setting: true. See
settings a deployment reads.
From kit 0.32.0, the description says what an agent may choose:
capabilities.agentChoices, deployment.modelOptions (the models an agent may
switch to and the channels a model choice applies to) and locked: true on each
tool an agent always has. From kit 0.33.0, capabilities.toolsOptIn says an
agent may turn on any tool the deployment has that is not locked, for any
channel. A locked tool stays on the channels the deployment puts it on. See
models and tools per agent.
From kit 0.36.0, a deployment that lets each agent choose relay or realtime
voice lists its realtime speech models under deployment.realtimeOptions, each
with its provider, its voices and its default voice, and one marked default.
An agent chooses one with its voice entry's model and voice. See
relay or realtime on voice.
From kit 0.31.1, each tool under description.tools carries parameters: the
JSON Schema of the arguments the model passes it. A field with a default is not
in required. A tool on an older kit has no parameters, and neither does one
whose schema the deployment could not convert, nor any tool when the
description would pass 256 KB even without its prompt contents. None of these
means the tool takes no arguments.
From kit 0.35.0, the description says which outcomes the deployment has. A tool
under description.tools whose success names one carries outcome, such as
"booked", and deployment.outcomes lists them all in order, shallowest
first. escalate_to_human is listed on the text channels, locked, with
outcome: "escalated", unless the project sets lifecycle.textHandoffToolId, which
leaves it without one. An agent's default outcomes are worked out from these.
See what describe announces.
From kit 0.38.0, deployment.sessionIdle lists the channels the deployment
ends on silence and the minutes it falls back to on a channel where the agent
stores neither that channel's minutes nor a default. See
when a text conversation ends.
Prompt bodies are omitted unless you ask for them, because they are large. The whole result is capped at 200 KB; over that, prompts are dropped first, and the variable list goes with them, then the signed description, then the text is truncated.
<channel>_send_message
These are the conversation tools, and they vary per agent. There is no fixed list.
Every deployment publishes a manifest at
/.well-known/subcore-agent.json describing each channel and the doors into it.
The server reads that manifest and emits one tool per JSON message door, named
after the channel. A typical kit deployment serving voice, SMS, chat and email
produces three:
| Tool | From |
|---|---|
chat_send_message | POST /chat/message |
sms_send_message | POST /sms/message |
email_send_message | POST /email/message |
The voice channel produces none, because its only door is the form-encoded Twilio webhook. Add a channel to a deployment and its tool appears here with no change to the MCP server.
When a channel publishes a second JSON door, the extra one keeps its door id as a
suffix — chat_send_message_<doorId>. The primary door is the one with id api.
Arguments
The schema comes from the door's own payload documentation, so the fields
differ by channel. Every one of these tools also has:
| Argument | Type | Description |
|---|---|---|
sessionId | string | Omit to start a new conversation. Pass an existing id only to continue that one. See session semantics |
core | string | Agent slug. Optional when the key reaches exactly one agent |
For a typical kit deployment, the channel-specific fields are:
| Tool | Fields |
|---|---|
chat_send_message | message (required), customerPhone |
sms_send_message | body (required), from |
email_send_message | body (required), from, subject, customerPhone |
The customer's text is capped at 4000 characters through the playground door, whatever the channel's own limit is. Any field beyond the ones the playground itself understands is passed to the deployment as prompt context, capped at 4000 bytes serialized.
Two conveniences for the CRM lookup, which keys on a phone number rather than an
address: a from that looks like a phone number is used as customerPhone when
none was given, and a from that is an email address is also published to the
deployment as fromAddress.
What a turn actually does
The turn runs through the playground door of the agent's deployment, under
the agent's routing key (/a/{routingKey}/api/playground/message), not the
customer traffic door. A deployment on a kit before 0.31.0 refuses that path,
because the key is 7 characters. It uses that agent's real instructions and
tools for the channel. For chat those are
the same pipeline. For voice and SMS the playground is a simulation, and the
result says so.
Nothing is delivered to a customer. The conversation is stored in Subcore
telemetry as a test session — run_type: "test" — so it never mixes into your
production numbers.
The response carries the reply text plus the sessionId used, the channel, the
agent, whether the session was newly created, the tool calls the agent made, and
its lifecycle events.
Doors that never become tools
| Excluded | Why |
|---|---|
POST /outbound | Places a real phone call, on your bill |
POST /sms/send | Sends a real text, on your bill |
| Form-encoded doors | Composed by the vendor's webhook, not by a caller. A tool that wrote one would be a tool for forging a webhook |
Non-POST doors | Not a message door |
The two cost-incurring paths are refused by path, before any other rule. There is no configuration that turns them on.
record_sent_reply
Record the reply that was actually sent to the customer into an existing conversation, without running a turn.
| Argument | Type | Description |
|---|---|---|
sessionId | string | Required. Must come from a previous <channel>_send_message response |
text | string | Required. The exact text that was sent. At most 20,000 characters |
core | string | Agent slug. Optional when the key reaches exactly one agent |
Use it after you have sent a possibly-edited agent reply through your own
channel, so the agent's memory of the conversation matches what the customer
actually received and telemetry carries a reply.recorded event. This is the
last step of the agent-as-a-brain walkthrough.
It never creates a conversation. A sessionId that does not already exist on
the deployment is an error, not an invitation to invent a session.
It records under the agent's routing key
(/a/{routingKey}/api/playground/record), where the conversation belongs. The
kit answers there from 0.31.0; older kits record only on the bare door, which
does not know a conversation an agent's routing key opened. So the tool is only
listed when a reachable deployment advertises the record capability on kit
0.31.0 or later. On an older deployment it is absent, and calling it by name is
refused with a message naming the version.
Telemetry
Four tools over the same query layer the read API serves, so the
semantics are identical. They appear only when the key carries
permissions.telemetry.
list_sessions
Recent conversation sessions (sids) for this organization, most recently active first, with pagination.
| Argument | Type | Description |
|---|---|---|
core | string | Agent slug. Omit to span every reachable agent |
channel | string | voice, sms, chat, email, … |
runType | string | production is customer traffic, test is playground and MCP turns, eval is evaluation runs |
from | string | ISO 8601. Only sessions still active at or after this instant |
to | string | ISO 8601. Only sessions that had started by this instant |
cursor | string | nextCursor from a previous page |
limit | integer | 1–100, default 25 |
get_session
Every event of one session in the order it happened — the transcript plus tool calls.
| Argument | Type | Description |
|---|---|---|
sid | string | Required. The session id |
cursor | string | nextCursor from a previous page |
limit | integer | 1–100, default 25 |
search_events
Search telemetry events by type, tool, agent and time window, newest first.
| Argument | Type | Description |
|---|---|---|
core | string | Agent slug. Omit to span every reachable agent |
eventType | string | e.g. tool.called, session.ended |
toolId | string | |
agentId | string | |
from | string | ISO 8601 lower bound on the event time, inclusive |
to | string | ISO 8601 upper bound on the event time, inclusive |
cursor | string | nextCursor from a previous page |
limit | integer | 1–100, default 25 |
get_eval_run
Every event of one evaluation run (
run_type"eval"), by run id.
| Argument | Type | Description |
|---|---|---|
runId | string | Required |
cursor | string | nextCursor from a previous page |
limit | integer | 1–100, default 25 |
All four return { items, nextCursor } and honour permissions.cores exactly as
the read API does. get_session and get_eval_run have no core argument, so a
key restricted to named cores is told the object does not exist rather than that
it may not see it.
Outcomes
Three tools over the same handlers as the agents API's
outcome routes, so the defaults, the whole-list replace
and the refusals are the same through either. They read and change an agent's
outcomes: the ordered rules that give each of its sessions one outcome in the
console's Sessions list. They appear only when the key carries
permissions.manageAgents, the permission
those routes need, and a key without it calling one by name is refused.
Each answers as the route does, { outcomes: { fallback, rules }, source },
where source is "stored" for a list someone saved and "defaults" for the
list worked out from the agent's deployment until then. A refusal comes back as
an error result that starts with its code, such as INVALID_OUTCOMES:.
get_agent_outcomes
An agent's outcomes: the ordered rules that give each of its sessions one outcome in the console's Sessions list.
| Argument | Type | Description |
|---|---|---|
core | string | Agent slug. Optional when the key reaches exactly one agent |
Returns the list in force: the saved one, or the defaults.
set_agent_outcomes
Replace an agent's whole outcome list: pass every rule, in order, since any rule left out is removed.
| Argument | Type | Description |
|---|---|---|
fallback | string | Required. What names a session no rule matches, such as "Answered" |
rules | array | Required. At most 30 { name, when } rules, in order. The last one whose condition holds names a session, so the deeper outcomes go later. when is exactly one of { "tool": "<tool id>" } or { "event": "transferred" | "voicemail" | "hung_up" | "agent_ended" } |
core | string | Agent slug. Optional when the key reaches exactly one agent |
Each name is 1 to 40 characters once trimmed, unique ignoring case, and stored
trimmed. The change relabels the agent's past sessions too, since the console
decides outcomes when it reads them. Call get_agent_outcomes first and send
the list back with your change. See
replace the outcomes for every
refusal.
reset_agent_outcomes
Put an agent back on the default outcomes, removing the list someone saved.
| Argument | Type | Description |
|---|---|---|
core | string | Agent slug. Optional when the key reaches exactly one agent |
Answers with the defaults now in force, and the same for an agent already on them.
All three honour permissions.cores, as every tool with a core argument does.
get_agent_models
The models an agent may choose, per channel, from what its deployment offers.
| Argument | Type | Description |
|---|---|---|
core | string | Agent slug. Optional when the key reaches exactly one agent |
deployment | string | A deployment name, as list_agents shows it. Lists what that deployment offers instead of the agent's own, for an agent you are about to move. Optional |
It calls the same handler as
GET /agents/{slug}/deployment/models,
so it answers { deployment, readAt, channels } from the copy of the
deployment's description the console keeps. That copy is what a new model or
voice for the agent is
checked against.
Without deployment, it lists the agent's own deployment, and refuses an agent
that names none. Unlike describe_deployment, it never falls back to the only
deployment the key reaches.
It appears only when the key carries permissions.manageAgents, as the outcome
tools do. It honours permissions.cores, and deployment must be one the key
reaches.