Connectors
The connector lifecycle agent-core owns: OpenAPI and remote MCP connectors, configure, connect, and reconnect.
agent-core owns the connector lifecycle for external services — MCP
servers, OpenAPI APIs, OAuth providers, and data sources. This is the runtime
that turns a configured connector into live, callable tools, and serves the
/connectors/* REST surface. For the contract shape of a connector
(config schema, capabilities, scope behavior, and the
resolveOsUri contract), see
package-system connectors.
The state machine
Every connector moves through a managed lifecycle:
unset → configured → connecting → connected → error → failed
↘ disabled- Configs are persisted as JSON in the project data zone, in a directory per project — see Connectors are project-scoped.
- Instance state is held in memory with status, capabilities, scope, and health, keyed by project and connector id.
- Auth is not part of the connector subsystem. A connector that needs a secret declares it in its package manifest and reads it through the platform credential resolver — see below.
Connectors are project-scoped
A connector belongs to one project, and the project is part of where its config lives — inside that project's own data zone. A separate platform data zone holds the connectors a package declares for the whole deployment; those stay visible and usable in every project. The first project loaded at startup does not hold other projects' connector files.
What follows from that:
- A caller sees its own project's connectors plus the platform ones — never another project's, on any surface: the REST listing, the health and tool reads, and the set of connector-backed MCP servers merged into an agent's session all resolve the same way.
- An id that belongs to another project answers exactly the same
404as an id that does not exist. Distinguishable answers would let a caller discover other projects' connector ids by reading error text. - A connector id is only unique within a project. Two projects may hold the same id, and each keeps its own configuration, connection state and generated tools; a project's own connector always wins over a platform one of the same name.
- Creating a connector requires a resolved project — a caller without one has nowhere to create it except the shared lane, so the request is refused.
- A package that is not first-party can only ever register connectors into the project it belongs to; it can never reach the platform lane.
Deployments that ran an earlier version keep their connectors: existing records are moved into their owning project's directory at startup, even if earlier boots placed them under another project. The copy is verified before the old file is removed and can resume after interruption. Differing records claiming one id both remain for a human to resolve; records with no usable existing project remain inactive, never published into every project. Operators must resolve or archive these leftovers outside project data before declaring the old data zones clean.
Reading versus changing
The /connectors/* surface is two-tiered. Reading — the connector list, one
connector's health, the tools it generated — sits at the ordinary read tier
(core.observe), so anyone who can observe a project can see what it is wired
to. Changing one — create, configure, connect, disconnect, enable, disable,
delete — additionally requires core.connectors, a feature with no default
grant below the admin tier and grantable to any custom role. See
roles and features.
The read tier sees display fields only — name, type, transport, target URL,
status and health. A connector's environment, its auth configuration and any
local process definition (command, arguments, working directory) are never
returned to it. Creating or repointing a connector is a configuration change to
what the platform will talk to, not an act of observation, so it takes the
stronger feature — and a connector created here is always a remote http or
streamable-http endpoint: a local command, arguments or working directory is
refused.
Two connector kinds
- OpenAPI connectors parse an OpenAPI 3.x spec and generate one tool per operation, executed through a hardened HTTP path. Every outbound request — fetching the spec URL and calling each generated operation — is SSRF-guarded and pinned to a validated public IP: a spec or API URL that resolves to a private/internal address is refused before any connection opens.
- MCP connectors bridge the tools of a remote MCP server reached over
httporstreamable-http. A local (stdio) MCP server is never started inside the platform process.
What it does not own
Most filesystem source connectors (the URI-native source kinds local —
serving packages:// and data:// — plus brain and webtop) are routed to
the kernel source registry and owned by brain-core and
machine-core, not by this subsystem. This page covers the
service connector lifecycle; brain-core owns the filesystem source
connectors that feed the
URI filesystem.
The one exception is agent-core's own host source kind — the bridge to the
operator's host filesystem over the host broker. It is declared in agent-core's
manifest and implemented in this package, but like every other source kind it is
registered with the kernel source registry rather than driven by the connector
lifecycle below. Every path it reaches is clamped by the operator-owned broker
ceiling and the source URI policy, and attaching it requires
drive.mount.host + drive.mount.privileged — see the
host filesystem connector.
Credential discipline
Connectors have no credential store of their own. A connector that needs a
secret declares it in its package manifest's credentials[] array and reads it
through the CredentialResolver the loader injects into the owning package —
the same encrypted, audited store every other capability uses. See
Credentials.
One limitation to know before you plan around it: the connector lifecycle itself currently supplies no auth headers. Its header-resolution seam returns an empty map, so the OpenAPI executor and the MCP session factory receive nothing from it, and no caller scope is threaded into a connector. A connector that needs authentication must resolve the credential itself, in its own package code, through the injected resolver.
Earlier versions carried a connector-local credential path that wrote secrets to a plaintext file and to the process environment. It was removed: it was not scoped, not encrypted, not audited, invisible to the credential catalog, and it did not survive a restart. There is one credential definition, and this is not a second one.
The child-process env is still sanitized before spawn, and secrets never appear in tool inputs or model context — the same rule the execute shell follows.