Tools and connections
An agent's tools come from four places, all selected in its revision:
- Built-in toolsets, which the Service runs itself: files, terminal, web, asset publication and agent configuration.
- Connections: remote MCP servers and app accounts of a connector provider such as Composio.
- Client tools, which your application executes and answers through resume.
- Skills, which add instructions and files; see Skills and secrets.
Every tool has a permission in the revision: allow, ask for approval, have a reviewer model decide, or deny.
Built-in toolsets
GET /api/v1/workspaces/{workspace_id}/toolsets returns the catalog with each tool's key, the name the model sees, its default state and permission, and its configuration schema.
| Toolset | Tools (model names) | Default |
|---|---|---|
files |
view, write, edit, multi_edit, mkdir, move, copy, delete, ls, glob, grep |
Enabled |
shell (Terminal) |
shell_exec, shell_info, shell_wait, shell_input, shell_signal |
Enabled |
web |
search, scrape, fetch, download |
Each tool disabled |
assets |
publish_asset |
Disabled |
configuration |
find_resources, read_resource, describe_agent_config, create_agent, create_agent_revision |
Disabled; see Configuration assistant |
The files and terminal tools act on the run's mounted environments and are offered to the model only when the run has one. publish_asset turns a file from an environment into a workspace asset.
Web search and scrape
search and scrape call a web provider: add one under Workspace settings → Providers → Web, then select it in each tool's configuration. fetch and download use the Service's own HTTP client and need no provider. All four pass the deployment's outbound policy.
| Provider type | Operations | Credential |
|---|---|---|
duckduckgo |
search | none |
brave, perplexity, serpapi |
search | api_key |
exa, parallel, tavily, firecrawl, jina, tinyfish |
search, scrape | api_key |
Enable each web tool explicitly; enabling the toolset alone enables none of them. Tool configuration:
{
"web": {
"enabled": true,
"tools": {
"search": {"enabled": true, "config": {"provider_id": "wprov_...", "max_results": 5}},
"fetch": {"enabled": true, "config": {"deny_domains": ["internal.example.com"]}}
}
}
}
searchtakesprovider_idandmax_results(1–10).scrapetakesprovider_idandmax_content_bytes(up to 4 MiB).fetchreturns up to 256 KiB.- Every web tool takes
allow_domainsanddeny_domains; a bare host includes its subdomains, and an empty list means unrestricted. None of the built-in provider types supports a domain-restricted scrape, soscraperefuses domain lists. - The provider's type must serve the operation. The check happens when the revision is saved.
Connections
A connection is a source of tools with one credential. Connections belong to a workspace; any member with run can use them in runs, and changing them needs write. In Console, open Connections → New connection.
| Kind | type |
auth |
|---|---|---|
| Remote MCP server (Streamable HTTP) | mcp |
none, bearer, headers or oauth |
| Connector app account | The connector provider's type, such as composio |
account |
A connection's status is pending until it has a usable credential, ready, or reauthorization_required after a lost token refresh. failure describes the last failed authorization operation. Credentials are write-only: views show credential_configured and client_secret_configured. POST …/authorize is browser authorization; an MCP connection whose auth is not oauth has none, and answers 409 conflict reason no_browser_authorization.
Connections have no delete operation. PATCH {"enabled": false} stops all use at once, including calls of running agents; {"enabled": true} restores it. POST …/revoke clears the credential at once and, as a best effort, asks the provider to revoke it remotely, reporting the outcome in remote_revocation (revoked, failed, or skipped when there was nothing to revoke remotely). PATCH and revoke take the connection's If-Match; re-read it before changing it, because authorizations and token refreshes change its version. Revoke also works on a connection that is already disabled, or whose workspace is archived: dropping a credential is offboarding, not a change.
Remote MCP servers
curl -X POST "$A13N_URL/api/v1/workspaces/$WORKSPACE/connections" \
-H "Authorization: Bearer $A13N_API_KEY" -H "Content-Type: application/json" \
-d '{"type": "mcp", "name": "Docs search", "auth": "bearer",
"config": {"url": "https://mcp.example.com/mcp", "tools": ["search_docs", "read_doc"]},
"credential": {"token": "..."}}'
config.urlmust pass the outbound policy. Credentials never go in the URL: a query parameter named like a credential (api_key,token, ...) is refused; useheadersauthentication instead.config.toolsoptionally restricts the connection to named tools. Without it, the connection exposes every tool the server lists, and a run fails withconnection_tools_exceededif the server lists more than 128.bearertakes{"token": "..."}, sent asAuthorization: Bearer.headerslists header names inconfig.headersand takes their values as{"headers": {"x-api-key": "..."}}. Transport and protocol headers (host,content-type,cookie,mcp-*,sec-*,proxy-*, ...) cannot be set.- Changing the URL, authentication or OAuth client drops the stored credential. Changing only
config.toolskeeps it.
OAuth
With auth: "oauth", the connection obtains its token from the MCP server's authorization server. The connection holds one credential that serves every run in the workspace, whoever authorized it.
- Browser authorization (
grant_type: "authorization_code", the default). Choose Authorize connection in Console, or callPOST …/connections/{connection_id}/authorizewith{"return_url": ...}and send the user to the returnedredirect_urlbeforeexpires_at;authorization_pendingstays true until then. The Service discovers the authorization server, uses PKCE (S256) and resource indicators, completes the flow at its own callback, and redirects the browser toreturn_urlwith the outcome. A browser flow always needs a login session and is bound to the browser that started it, by a cookie scoped to the callback path; the callback fails withbrowser_mismatchif another browser completes it. An API key cannot start one (403 forbidden): a browser flow hands out a link that anyone holding it could complete, and only a person's own session may do that. A callback afterexpires_atfails withauthorization_expired. Starting a new flow drops only a completion already in progress; it keeps the working credential and an outstanding refresh, which continues until the new flow completes. - Client registration. Without
config.oauth.client_id, each authorization registers a public client dynamically. For a client registered in advance, setclient_idandtoken_endpoint_auth_method(none,client_secret_basicorclient_secret_post) and supply the write-onlyclient_secret. Register the Service's redirect URI with the provider:GET /api/v1/connections/redirect-urireturns it ({public_url}/api/v1/connections/callback), and Console shows it in the connection form. - Machine credential (
grant_type: "client_credentials", with a client secret).authorizeobtains the token directly, without a browser, and returnsredirect_url: null. Like a bearer token, it acts for every run of the workspace. This is the one authorization an API key may call. config.oauth.scopesrequests specific scopes; empty requests the scopes the server advertises.- Tokens refresh on use when the server issued a refresh token, through the connection's single refresh operation; concurrent callers wait for its result, and a cancelled caller never loses a rotated credential. The credential is cleared (
reauthorization_required) only when the server refuses it asinvalid_grant, or the request's outcome is unknown because it may have already reached the server. A request the Service never sent keeps the credential: the outbound policy refusing it (token_endpoint_deniedfor the token request,authorization_server_deniedduring discovery) or a connection failure before anything went out (token_endpoint_unreachable). A new authorization revokes the grant it replaces, as a best effort, unless the replacement came from the same OAuth client, since revoking it could also end the new grant.
return_url must be a page on the origin of server.public_url, or an exact URL in the deployment's providers.return_urls; Console sends {its origin}/connections/callback. The public callback is rate limited per client address.
Composio connections
Composio hosts app integrations and the external accounts' credentials. Configure it once, then connect accounts per app:
- Add a connector provider of type
composiowith your Composio project API key: Workspace settings → Providers → Connector (or the organization's providers to share it), orPOST /api/v1/organizations/{organization_id}/connector-providerswith{"workspace_id": ..., "type": "composio", "name": ..., "credential": {"api_key": "..."}}. - Browse apps and their actions:
GET /api/v1/workspaces/{workspace_id}/connector-providers/{provider_id}/apps(query,refresh=true),…/apps/{app}and…/apps/{app}/actions. - Create a connection with
type: "composio",auth: "account", theconnector_provider_id, andconfignaming theapp, the pinnedactions(1–128) andsetup:auth_config_id(an existing Composio auth config, orcreate:<SCHEME>such ascreate:OAUTH2) and the pinnedtoolkit_version(YYYYMMDD_NN). - Authorize it (a login session, like any browser authorization): the user completes Composio's hosted account setup in the browser and returns through the Service's callback. The connection then binds that one external account; Composio keeps and refreshes its tokens.
Each connection binds one account and exposes exactly its pinned actions. Tool calls send a stable request ID per tool call; when an action's outcome is unknown, the agent is told to check the external state before calling it again.
Test and discover tools
POST …/connections/{connection_id}/testdiscovers tools now and records the outcome inlast_test; it needsrun.GET …/connections/{connection_id}/toolslists tools with their input and output schemas and annotations, cached forproviders.discovery_ttl. Annotations such as read-only hints are shown as declared and never grant permission.- Before a connector connection's account is bound,
testfails and/toolsreturns the connector app's action catalogue instead of live discovery.
A connection exposes at most 128 tools to a model.
Use a connection in an agent
A revision lists connections in connection_tools:
{
"connection_tools": [
{"connection_id": "conn_...", "tools": ["search_docs"], "permission": "allow"},
{"connection_id": "conn_...", "tools": null, "defer_loading": true, "permission": "ask",
"permissions": {"delete_page": "deny"}}
]
}
tools: nullselects every tool the connection exposes; a list selects up to 128 of them.defer_loading(MCP only) lets the model find tools through tool search instead of receiving every definition up front.permissionapplies to the connection's tools, andpermissionsoverrides it per tool.- A revision can select up to 128 connections. Saving it checks that each connection is enabled and exposes the selected tools.
At execution, each connection must be ready. Before every call the Service checks that the connection is still enabled and authorized and that the run may still spend; otherwise the call is not sent. Calls are bounded by providers.tool_call_seconds and are never retried automatically.
Caller headers
A thread can carry non-credential context for its MCP connections, such as a conversation or tenant identifier, in mcp_headers: {connection_id: {header: value}}. See threads. Header names that the connection's own authentication uses are refused.
MCP server suggestions
GET /api/v1/mcp-servers?query=... lists well-known remote MCP servers with their URL, authentication and requirements; Console uses it to prefill new connections. The Service ships a list of suggestions, and operators add or replace entries by key with providers.mcp_servers:
[[providers.mcp_servers]]
key = "handbook"
name = "Team handbook"
description = "Search the internal handbook."
url = "https://mcp.example.com/handbook"
auth = "oauth"
A suggestion only prefills the form; the connection is validated like any other.