arizuko › concepts › connectors
connectors
A connector gives the agent a tool it did not ship with, such as a DNS registrar or an internal API. You write one TOML block. routd makes the call on the host, inserts the secret into the request and returns the response body to the agent.
The agent gets the tool and the host keeps the credential
Inside the guest, an MCP tool called cloudflare_dns_list exists, and the connector puts no CF_API_TOKEN in the environment, the filesystem or /proc. A shell:env:<KEY> grant for the same key would still add it to the guest environment.
One block has one of two transports
Every connector is one [[connector]] block, and transport picks its dispatch path. A field that one transport does not use is absent for it, and no field has two meanings:
mcp- The default when you leave out
transport. The far side is already an MCP server over HTTP. It reports its own tool list, so you declare no tools. It reads onlyheaders, because an HTTP MCP server takes its credential there. rest- The far side is an ordinary REST API with no MCP support. You declare one
[[connector.tool]]per endpoint, and you can use all three credential sinks.
Connectors use HTTP only. No stdio transport exists.
A REST connector needs one block and one tool entry
# connectors.toml in the instance directory
[[connector]]
name = "cloudflare"
transport = "rest"
url = "https://api.cloudflare.com/client/v4"
secrets = ["CF_API_TOKEN"]
headers = { Authorization = "Bearer {secret:CF_API_TOKEN}" }
[[connector.tool]]
name = "dns_list"
description = "List DNS records for a Cloudflare zone."
scope = "ext:cloudflare:dns:read"
method = "GET"
path = "/zones/{zone_id}/dns_records"
The agent sees a tool named cloudflare_dns_list, in the form <provider>_<tool>. The tool takes one argument, zone_id, because {zone_id} is a path placeholder, and the loader writes the schema for placeholders. Any other argument becomes the query string on a GET or DELETE, and the JSON body on a POST, PUT or PATCH.
A placeholder puts the secret where it belongs
No list of auth methods exists. You write {secret:KEY} at the place in the request where the value belongs, and routd renders it on each call into one of three sinks:
| sink | example |
|---|---|
| headers | { Authorization = "Bearer {secret:CF_API_TOKEN}" } |
| query | { ApiKey = "{secret:NAMECHEAP_KEY}" } |
| body | { apikey = "{secret:PB_API_KEY}", secretapikey = "{secret:PB_SECRET}" } |
“Bearer” is a literal prefix that you typed, not a mode that the loader knows.
Three things happen to the value on every call:
routdlooks it up for the triggering user. The own key of the user wins over the folder default, so one connector serves a team whose members each hold their own token.routdscrubs it from the response before the text reaches the agent. The scrub replaces each exact secret value with«redacted». A response that echoes the key in another form, such as base64, passes.- If a declared key resolves to nothing,
routddrops the whole entry. An unset secret sends no header at all, and the literal text{secret:CF_API_TOKEN}never goes upstream.
The key is a row in the secrets table of routd, set with arizuko secret or at /dash/me/secrets. It is not in the daemon environment. One name can exist in both places with different values. GH_TOKEN in the instance .env belongs to a daemon process, and GH_TOKEN in the secrets of a folder is what the connector sends. The connector reads only the row of the folder, through the broker, so a token in .env says nothing about what the connector has.
The grant decides which folder sees the tool
Each tool carries a scope in the form ext:<service>:<operation>. Grants filter registration as for every other MCP tool: the tool joins tools/list only where Authorize(folder, scope) passes. A folder without the grant gets no refusal. It does not see the tool. The check at call time runs anyway, as a second defence.
A grant per folder decides which folder gets which tool:
arizuko group -i <instance> grant folder:main/infra 'main/infra/**' 'ext:cloudflare:dns:read'
Four DNS registrars ship as descriptors
arizuko embeds descriptors in routd/extproviders/. A descriptor does nothing until the operator sets the token of that provider.
| provider | tools |
|---|---|
| Cloudflare | dns_list, dns_create, dns_delete, zone_list |
| Porkbun | dns_list, dns_create, dns_delete |
| Gandi | dns_list, dns_set |
| Namecheap | dns_list |
Connectors have three limits
- Egress rules of the guest do not apply
- The call leaves from
routdon the host, not from the network of the agent. Use the grant, not the allowlist, to decide what a folder can reach. - The agent gets only the response body
- No status line and no headers reach the agent. If the agent must branch on a status code, a connector is the wrong tool.
- An
mcpconnector trusts the tool list of the far side - It discovers tools at connect time. If the server adds a tool, your agent gets that tool on the next connect. The grant still applies, but nothing you wrote lists the new tool.
Four pages continue from here
how-to / add a connector adds one step by step. reference / mcp describes the descriptor field by field and every built-in tool. grants explains how the scope is checked. secrets explains where the value is stored and who can read it.