.mcp.json and the [mcp_servers] tables of Codex’s .codex/config.toml, on either harness. Aether reads both files through GitHub at the commit the task’s base branch names when the task is created, and keeps that commit for the whole task. Values come from the project’s environment variables. The servers are the task’s own: nothing is added to the organization’s catalog.
An entry Aether cannot use never blocks a task: it is skipped, and the task is created and runs without it. Every skip is shown with its file, its server and why: in the composer before you send, on the task page, in the CLI’s aether task create output, and as a line in the Slack and Linear replies that start the task. Only a config.toml server with required = true refuses the task instead.
Remote servers
A server with aurl ("type": "http" in .mcp.json) is reached over Streamable HTTP through Aether’s MCP gateway, so its URL and headers never enter the workspace. ${VAR} and ${VAR:-default} in .mcp.json, and bearer_token_env_var and env_http_headers in config.toml, resolve from the project’s environment variables. A server that sets no Authorization header and has the URL of a catalog server the project enables uses that server’s connection, under the project’s credential policy. A server that sets no Authorization and has no catalog server’s URL is asked whether it needs a credential; if it does, it needs an org admin to add a catalog server with its URL. An optional server whose connection the task cannot use is skipped, saying whose connection is missing.
Local (stdio) servers
A server with acommand runs inside the task’s workspace as a catalog local server does: Aether’s workspace service starts the process and both harnesses reach it as one more MCP server. The harness never starts it.
Supported commands
The command is a package runner with a package pinned to one release:-y (or --yes) is optional for npx; uvx --from <package>==<version> <package> is the same as uvx <package>@<version>. Arguments after the package go to the server unchanged.
- The version is pinned. An npm package takes a full semver version (
1.2.3), a PyPI package an exact PEP 440 version. A package with no version,latest, a tag, a range or a wildcard is skipped: the task would run whichever release is newest when it starts. - Other commands are skipped.
node server.js,python -m …withoutuvx,docker,uv run, a binary or script in the repository, a path tonpx, and package-runner options other than the ones above.
Environment
The process getsPATH, HOME, the npm and uv package caches, and the variables its file declares — nothing of the agent’s environment, and none of the project’s other environment variables.
.mcp.json: eachenvvalue expands${VAR}and${VAR:-default}from the project’s environment variables..codex/config.toml:envvalues are used as written (Codex expands nothing in them). Each name inenv_varsis set from the project’s environment variable of that name, as Codex forwards it from the environment it runs in. A name may be inenvorenv_vars, not both.
command and args never reference a variable — a project’s environment variables reach a local server only through its env. A server with cwd in config.toml is skipped: a local server runs in the task’s repository checkout.
Required and disabled servers
A.mcp.json server is optional; one with "disabled": true is left out. A config.toml server is optional unless it sets required = true, as Codex reads it; one with enabled = false is left out. An optional server Aether cannot use is skipped, and one that does not start within 90 seconds is left out of the turn, which runs without it. A required server Aether cannot use refuses the task at creation with the code mcp_repo_config_invalid, naming the file and the server, and fails a later turn the same way; one that does not start within 90 seconds fails the turn, naming it.
When both files, or the organization, declare a server
- The same name in both files is one server when both declare the same URL and headers, or the same command, arguments and environment (
env_vars = ["TOKEN"]inconfig.tomlis the same as"TOKEN": "${TOKEN}"in.mcp.json). Two different declarations under one name are not one server: the task is given.mcp.json’s, andconfig.toml’s is skipped. Names become server keys as the harnesses write them into tool names, lowercased. - A name the organization’s catalog already uses is the organization’s server: the task uses it instead, and its server list says why the repository’s copy was not given.
Skipped, and why
Each skip names its file and its server, or only the file when the file does not parse, and one of these reasons. The task runs without the server; fix the file or the project and the next task, or the task’s next turn for a variable, gets it.
A variable removed from the project after a task was created, or a connection that went away, leaves its server out of the task’s next turn, and the task page names it as skipped; a connection made since gives the next turn the server again. A required server fails the turn instead, with
mcp_repo_config_invalid and the same message.