arizuko

arizuko › howto › Add a connector

connectors

A connector gives an agent a tool that calls an external API. The credential of that API stays on the host and never enters the guest. You write one TOML block, store the secret, and grant the scope to the folders that get the tool. This page adds one connector for a real API.

Connectors explains what a connector is and why the credential stays on the host.

The far side decides the transport

If the far side is an MCP server that answers over HTTP, use transport = "mcp" and declare no tools, because the server publishes its own list. If it is a normal REST API, use transport = "rest" and one [[connector.tool]] for each endpoint that you expose. The steps below use the REST case, because it has more choices.

One block in connectors.toml declares the connector

routd reads connectors.toml in the instance directory, next to docker-compose.yml. Make the file if it does not exist.

[[connector]]
name      = "statuspage"
transport = "rest"
url       = "https://api.statuspage.io/v1"
secrets   = ["STATUSPAGE_TOKEN"]
headers   = { Authorization = "OAuth {secret:STATUSPAGE_TOKEN}" }

  [[connector.tool]]
  name        = "incident_list"
  description = "List open incidents for a status page."
  scope       = "ext:statuspage:incident:read"
  method      = "GET"
  path        = "/pages/{page_id}/incidents/unresolved"

  [[connector.tool]]
  name        = "incident_create"
  description = "Open an incident on a status page."
  scope       = "ext:statuspage:incident:write"
  method      = "POST"
  path        = "/pages/{page_id}/incidents"

Two details decide whether the first call works. The Authorization value is a literal string, so you must write the correct scheme word: OAuth here, not Bearer. And {page_id} is a path placeholder, so it becomes a required argument of both tools. You do not declare it.

The secret goes into the secrets table, not into .env

At each call, the connector reads the keys in secrets from the secrets table. Set the key for the folder that uses it:

arizuko secret -i <instance> set main/infra STATUSPAGE_TOKEN --value 'sp_live_REPLACE_ME'

A person can store an own key. For turns that the person starts, that key wins over the folder key:

arizuko user-secret -i <instance> set 'slack:user/U123' STATUSPAGE_TOKEN --value 'sp_live_REPLACE_ME'

The connector reads only this row. The .env of the instance is the environment of the daemons. The connector never sends a STATUSPAGE_TOKEN from .env. The two places can hold different values under one name.

A folder sees the tool only with the grant

A folder without the scope of a tool does not see the tool in tools/list. Read and write are two scopes, so you can grant them separately:

arizuko group -i <instance> grant folder:main/infra 'main/infra/**' 'ext:statuspage:incident:read'
arizuko group -i <instance> grant folder:main/infra 'main/infra/**' 'ext:statuspage:incident:write'

routd loads connectors at start

routd reads the connectors when it starts, so restart the instance before you look for the tool:

sudo systemctl restart arizuko_<instance>

Then ask the agent in the granted folder for its tools. The tool name is <connector>_<tool>, here statuspage_incident_list. If the tool is missing, check the grant first. A missing scope gives no tool and no error message, by design.

Four problems have a known cause

The tool is missing
The folder does not hold the scope, or the block did not load. routd refuses a connector at load when its transport is not mcp or rest, or when a rest block has no [[connector.tool]]. The error names the connector.
The API returns 401
Check that the secret is set for the folder or the user in the secrets table, not in .env. When a key has no value, the connector sends no placeholder. It drops the whole header, so the request has no credential.
The agent says that the answer is empty
On success, a connector returns only the response body, with no status code. An endpoint that answers 204 with no body therefore looks like nothing happened. For a status of 400 or higher, the agent gets an error with the status and the body.
The call reaches a host outside the egress list of the folder
This is expected. The request leaves from routd on the host, not from the network of the agent, so the egress allowlist does not apply. The grant is the limit here.

A Connect flow needs an OAuth provider

If the service uses a "Connect" flow and not a pasted key, use an OAuth provider. arizuko then runs the OAuth flow and refreshes the token, and the connector reads the stored token.