Tools
Agents get tools from MCP (Model Context Protocol) servers. MCP sources are declared via the mcps field in either user settings ($HOME/.aether/settings.json) or project settings (.aether/settings.json). A common convention is to keep server definitions in a dedicated mcp.json file and reference it from mcps.
MCP config files
Section titled “MCP config files”Aether accepts either servers or the mcpServers alias at the top level:
{ "servers": { "coding": { "type": "in-memory", "args": ["--rules-dir", ".aether/skills"] } }}{ "mcpServers": { "playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@latest"] } }}If a server entry has a command and no type, it is treated as "type": "stdio".
Server types
Section titled “Server types”Built-in Aether servers run inside the agent process. No subprocess is launched.
{ "servers": { "coding": { "type": "in-memory", "args": ["--rules-dir", ".aether/skills"] }, "skills": { "type": "in-memory", "args": ["--dir", ".aether/skills"] }, "tasks": { "type": "in-memory" }, "subagents": { "type": "in-memory" }, "survey": { "type": "in-memory" }, "plan": { "type": "in-memory" } }}| Field | Type | Description |
|---|---|---|
type | "in-memory" | Required. |
args | string[] | Arguments passed to the registered in-memory server factory. |
input | any JSON or null | Optional factory input for in-memory servers that accept one. |
deferTools | boolean | object | true proxies every tool; an object partitions tools with include and exclude patterns. |
External MCP servers launched as subprocesses. Communication happens over JSON-RPC via stdin/stdout.
{ "servers": { "playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@latest"] }, "github": { "type": "stdio", "command": "gh-mcp", "env": { "GITHUB_TOKEN": "$GITHUB_TOKEN" } } }}| Field | Type | Description |
|---|---|---|
type | "stdio" | Optional when command is present. |
command | string | Executable to run. |
args | string[] | Command arguments. |
env | object | Environment variables for the subprocess. |
deferTools | boolean | object | true proxies every tool; an object partitions tools with include and exclude patterns. |
Remote MCP servers over streamable HTTP.
{ "servers": { "linear": { "type": "http", "url": "https://mcp.linear.app/mcp", "headers": { "Authorization": "Bearer $LINEAR_API_KEY" } } }}| Field | Type | Description |
|---|---|---|
type | "http" | Required. |
url | string | Streamable HTTP endpoint URL. |
headers | object | Optional headers. Aether currently forwards the Authorization header to the transport. |
oauth | object | Optional custom CIMD or pre-registered public OAuth client settings. See OAuth clients. |
deferTools | boolean | object | true proxies every tool; an object partitions tools with include and exclude patterns. |
Remote MCP servers over HTTP with Server-Sent Events. Internally, Aether uses the same HTTP transport path.
{ "servers": { "remote-tools": { "type": "sse", "url": "https://mcp.example.com/sse", "headers": { "Authorization": "Bearer $MCP_TOKEN" } } }}| Field | Type | Description |
|---|---|---|
type | "sse" | Required. |
url | string | SSE endpoint URL. |
headers | object | Optional headers. Aether currently forwards the Authorization header to the transport. |
oauth | object | Optional custom CIMD or pre-registered public OAuth client settings. See OAuth clients. |
deferTools | boolean | object | true proxies every tool; an object partitions tools with include and exclude patterns. |
OAuth clients
Section titled “OAuth clients”For http and sse servers that return an OAuth challenge, Aether defaults to its first-party Client ID Metadata Document (CIMD) at https://aether-agent.io/oauth/client-metadata.json. The callback listener binds http://localhost:3118/. If port 3118 is occupied, authentication fails rather than selecting an unregistered redirect URI.
Set oauth.clientMetadataUrl to use a custom CIMD, or oauth.clientId for a public client pre-registered with the provider. A pre-registered client ID takes priority when both are configured:
{ "servers": { "slack": { "type": "http", "url": "https://mcp.slack.com/mcp", "oauth": { "clientId": "1601185624273.8899143856786", "callbackPort": 3118 } } }}| Field | Type | Description |
|---|---|---|
oauth.clientId | string | Public OAuth client ID registered with the provider. Takes priority over CIMD. Supports $VAR / ${VAR} expansion. |
oauth.clientMetadataUrl | string | HTTPS URL of a custom Client ID Metadata Document. Supports variable expansion. |
oauth.callbackPort | number | Loopback callback port; defaults to 3118. The advertised URI is http://localhost:<callbackPort>/ and must exactly match the client metadata or registration. |
When the authorization server does not advertise CIMD support, Aether falls back to deprecated Dynamic Client Registration for compatibility. OAuth discovery, PKCE, scopes, offline_access, resource indicators, refresh, and issuer validation are handled by rmcp. An explicit bearer Authorization header remains authoritative and bypasses OAuth.
Built-in servers
Section titled “Built-in servers”| Server | Purpose | Args |
|---|---|---|
coding | File I/O, bash, grep/find, web fetch/search, and LSP-backed coding tools | --root-dir <path>, repeat --rules-dir <path>, --permission-mode always-allow|auto|always-ask, --disable-lsp |
skills | Skill discovery, slash-command prompts, and rules | Required: repeat --dir <path> |
tasks | Hierarchical task management | Optional --dir <base> for persistent storage |
subagents | Spawn child agents from loaded settings | --project-root <path> (alias: --dir; defaults to the workspace root) |
survey | Structured human-in-the-loop questions | No args |
plan | Plan prompt and markdown plan review | Optional --plans-dir <path>, --prompt-file <path>, plus optional trailing submit command |
MCP sources in settings
Section titled “MCP sources in settings”The mcps array in user or project settings can reference files, inline configs, optional files, or deferred files:
{ "mcps": [ ".aether/mcp.json", { "type": "file", "path": "${WORKSPACE}/.aether/local-mcp.json", "optional": true }, { "type": "file", "path": ".aether/external-mcp.json", "deferTools": true }, { "type": "inline", "servers": { "coding": { "type": "in-memory", "args": ["--rules-dir", ".aether/skills"] } } } ], "agents": [ { "name": "Build", "description": "Builds features", "model": "anthropic:claude-sonnet-4-5-20250929", "userInvocable": true, "prompts": [".aether/BUILD.md"] } ]}Agent-level mcps replace the top-level list when the agent’s array is non-empty. String shorthand entries are required file sources. Use a typed file object when you need deferTools or optional.
User, project, and MCP source resolution
Section titled “User, project, and MCP source resolution”Aether first merges user settings from $HOME/.aether/settings.json and project settings from .aether/settings.json. Set AETHER_HOME to use a different user settings directory. Project settings win when they define the same agent, and a non-empty project top-level mcps list replaces a user top-level mcps list. Within the selected agent, agent-level mcps replace top-level mcps when non-empty.
MCP file paths use resource path resolution:
- In project settings, plain relative paths resolve from the workspace root.
- In user settings, plain relative paths resolve from the Aether home (
$HOME/.aetherby default). ${WORKSPACE}always resolves to the current workspace root. This is the preferred way for a user-level agent to load a project’s.aether/mcp.json.- Other
$VARand${VAR}references fall through to the process environment. optional: trueskips a missing MCP file or unresolved variable instead of failing settings resolution.
{ "agents": [ { "name": "Build", "description": "Reusable coding agent", "model": "anthropic:claude-sonnet-4-5-20250929", "userInvocable": true, "prompts": ["BUILD.md"], "mcps": [ "mcp.json", { "type": "file", "path": "${WORKSPACE}/.aether/mcp.json", "optional": true } ] } ]}In this user-level example, mcp.json resolves to $HOME/.aether/mcp.json by default, while ${WORKSPACE}/.aether/mcp.json resolves to the project MCP config.
Merging and deferred tool behavior
Section titled “Merging and deferred tool behavior”Multiple MCP sources are loaded in order after settings resolution. Server names are unique keys; when two sources define the same server, the later source wins, including its deferTools configuration.
Tools can be deferred in two ways:
- Set
"deferTools": trueon a server entry to defer every tool, or use adeferToolsobject withincludeandexcluderules. - Reference a file source with
{ "type": "file", "path": "...", "deferTools": true }, which defers every tool loaded from that file.
deferTools and optional can be combined: { "type": "file", "path": "${WORKSPACE}/.aether/external-mcp.json", "deferTools": true, "optional": true }.
{ "servers": { "chrome-devtools": { "type": "stdio", "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest"], "deferTools": { "include": ["performance_*", "take_screenshot"], "exclude": ["performance_stop_trace"] } }, "linear": { "type": "http", "url": "https://mcp.linear.app/mcp", "deferTools": true } }}deferToolscan be set to: false, true, or an object; it defaults to false. false means every tool will be eagerly loaded into context. true defers loading every tool for progressive discovery. An object allows controlling which tools within a single MCP server are deferred via include and exclude rules.
Discover deferred tools progressively, without loading their schemas into the model’s initial context:
aether mcp --helpaether mcp <server> --helpaether mcp <server> <tool> --helpInvoke tools with exactly one JSON object supplied through --json or stdin. Tool calls time out after 600 seconds; pass --timeout <seconds> to change it. Because this is a real subprocess, it composes with ordinary Bash pipelines, redirects, &&, command substitution, and scripts:
aether mcp linear list_issues --json '{"state":"open"}' | jq .printf '%s' '{"state":"open"}' | aether mcp linear list_issuesOnly the private session socket and Aether executable directory are injected into Bash. Managed OAuth credentials stay inside Aether. Agent-level tools.allow and tools.deny filters govern both deferred discovery and execution, so denied tools remain unavailable.
Variable expansion
Section titled “Variable expansion”Variables are expanded in two places: MCP source paths in settings, and server fields inside MCP JSON files. Environment variables are expanded in stdio command, stdio args, stdio env values, in-memory args, HTTP/SSE url, and the HTTP/SSE Authorization header value. ${WORKSPACE} and ${AETHER_HOME} (the user-level Aether home, honoring the AETHER_HOME env var) are also available when server configs are converted into running MCP servers.
Supported syntax:
| Syntax | Meaning |
|---|---|
$VAR | Expand VAR |
${VAR} | Expand VAR |
$$ | Literal $ |
Missing variables are errors; they are not replaced with an empty string. For MCP source paths only, optional: true skips the missing source when a variable is undefined.
{ "servers": { "github": { "command": "$HOME/.local/bin/github-mcp", "env": { "GITHUB_TOKEN": "$GITHUB_TOKEN", "PROMPT": "cost is $$5" } } }}Tool filtering
Section titled “Tool filtering”The tools field in an agent entry restricts which discovered MCP tools the model can use.
Filters can be tool name patterns or (MCP) annotation matchers. allow is applied first; deny then removes matching tools, so deny wins.
Name patterns
Section titled “Name patterns”Tool names use server__tool with a double underscore. Patterns support exact names or a trailing * prefix wildcard only.
| Pattern | Matches |
|---|---|
coding__* | All coding server tools |
coding__read_file | Only the read_file tool |
coding__web_* | web_fetch and web_search |
tasks__* | All task management tools |
plan__submit_plan | Only the plan review tool |
coding__bash | Only the coding server’s bash tool |
Wildcards are only valid at the end of a pattern.
Annotation matchers
Section titled “Annotation matchers”MCP servers can attach tool annotations and you can filter tools based on those:
| Matcher field | MCP annotation | Meaning |
|---|---|---|
readOnly | readOnlyHint | The tool should not modify its environment. |
destructive | destructiveHint | The tool may perform destructive updates. |
idempotent | idempotentHint | Repeated calls with the same arguments should not add effects. |
openWorld | openWorldHint | The tool may interact with external systems or the network. |
Annotation-first read-only agent:
{ "agents": [ { "name": "ReadOnly", "description": "Can inspect code but cannot write files or run shell commands", "model": "openai:gpt-5.5", "userInvocable": true, "prompts": [".aether/READONLY.md"], "mcps": [".aether/mcp.json"], "tools": { "allow": [{ "readOnly": true }] } } ]}Read-only tools plus explicit plan tools:
{ "agents": [ { "name": "Planner", "description": "Can inspect code and write plan files", "model": "anthropic:claude-sonnet-4-5-20250929", "userInvocable": true, "mcps": [".aether/mcp.json"], "tools": { "allow": [{ "readOnly": true }, "plan__*", "skills__*"], "deny": ["coding__web_*"] } } ]}Full coding without bash:
{ "agents": [ { "name": "NoShell", "description": "Can use coding tools except shell execution", "model": "anthropic:claude-sonnet-4-5-20250929", "userInvocable": true, "prompts": [".aether/NOSHELL.md"], "mcps": [".aether/mcp.json"], "tools": { "allow": ["coding__*"], "deny": ["coding__bash"] } } ]}Deny destructive tools from mixed servers:
{ "agents": [ { "name": "SafeOps", "description": "Allows everything except tools annotated as destructive", "model": "anthropic:claude-sonnet-4-5-20250929", "userInvocable": true, "mcps": [".aether/mcp.json"], "tools": { "deny": [{ "destructive": true }] } } ]}If tools is omitted or empty, the agent can use all tools from its configured MCP servers.