Five ways to add behaviour: MCP servers, in-process extensions, skills, slash commands and hooks. All five are content that ends up in front of a model or a shell, and all five are designed on the assumption that the other side is hostile or broken.
The rule they share: nothing here can grant a permission. A skill, a hook and an MCP server can each tell earshot to do something, and each of those things goes through the same gate a request typed by the user does. What they cannot do is approve it.
The ACP server is another consumer of these boundaries, not a bypass around them. An editor can display and answer a permission request, but cannot turn a deny into an allow. MCP servers configured for an ACP session are still loaded through earshot's trust and permission rules; client-provided MCP process definitions are currently rejected.
MCP servers#
Configured under mcpServers in settings, in the shape Claude Code uses:
{
"mcpServers": {
"github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
"linear": { "type": "http", "url": "https://mcp.linear.app/mcp", "headers": { "Authorization": "Bearer …" } }
}
}stdio and streamable HTTP. The pre-2025 sse transport is not supported and is
reported rather than ignored.
Tools are namespaced server__tool, so two servers cannot collide, and a
server name may not itself contain __ — one that could would be able to
impersonate another server's tools.
Past 25 MCP tools, they are not listed in the prompt. Instead the model gets
one tool_search tool and finds them by what it wants to do. A search surfaces
at most ten at a time and they stay listed for the rest of the session. Nothing
about the gate changes: a surfaced tool is permission-checked exactly as it would
have been had it been listed all along, and tool_search itself grants nothing.
An MCP tool is never read-only. A server's readOnlyHint is an assertion by
the same party that wrote the tool, so earshot displays it and does not believe
it: every MCP call is serialised and gated. Rules name one tool, not one server:
{ "permissions": { "allow": ["Mcp(github__search_issues)"], "deny": ["Mcp(github__create_*)"] } }A server the project checked in does not start on its own. A stdio server
named in .earshot/settings.json is code the repository chose, and starting the
process it names is running that code. It is listed with an explanation until
you run earshot mcp trust <name>, which records approval in
.earshot/settings.local.json — not committed, so a repository cannot trust
itself in the next clone. Servers you define globally or locally start normally.
A server that fails to start, hangs, dies mid-session or floods its output is reported and skipped; the session runs with the tools it does have.
| Command | Effect |
|---|---|
earshot mcp list | What is configured, and which servers are not started |
earshot mcp trust <name> | Allow a project-scope server to start |
earshot mcp untrust <name> | Withdraw that |