Skip to main content
Commit your team’s MCP servers in the repository the way Claude Code and Codex read them, and Aether tasks get them too: Claude Code’s .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. A file Aether cannot honor refuses the task at creation, naming the file and the server. The composer shows the same refusal before you send.

Remote servers

A server with a url ("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.

Local (stdio) servers

A server with a command 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 refused: the task would run whichever release is newest when it starts.
  • Other commands are refused. node server.js, python -m … without uvx, docker, uv run, a binary or script in the repository, a path to npx, and package-runner options other than the ones above.

Environment

The process gets PATH, 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: each env value expands ${VAR} and ${VAR:-default} from the project’s environment variables.
  • .codex/config.toml: env values are used as written (Codex expands nothing in them). Each name in env_vars is set from the project’s environment variable of that name, as Codex forwards it from the environment it runs in. A name may be in env or env_vars, not both.
A variable the project does not set, with no default in the file, refuses the task at creation. A value that references a variable holds the project’s environment variables, so it is a secret: it is masked wherever the server’s output is logged. Literal values are not. command and args never reference a variable — a project’s environment variables reach a local server only through its env. cwd in config.toml is refused: a local server runs in the task’s repository checkout.
The process runs as the agent’s user in the workspace. It cannot inherit the agent’s environment, but it can read whatever the agent can read.

Required and disabled servers

A .mcp.json server is required; one with "disabled": true is skipped. A config.toml server is required unless it sets required = false; one with enabled = false is skipped. A required server that does not start within 90 seconds fails the turn, naming it; an optional one is left out and the turn runs without 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"] in config.toml is the same as "TOKEN": "${TOKEN}" in .mcp.json). Two different declarations under one name are refused. 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.

Errors

Task creation refuses with a message naming the file and the server; the composer’s readiness check refuses before you send, with the code mcp_repo_config_invalid and the same message: A variable removed from the project after a task was created makes its local server unavailable at the task’s next turn: a required one fails the turn with mcp_repo_config_invalid and the same message.