Documentation v0.3.2
Earshot / Extending

In-process extensions

Contribute local TypeScript or JavaScript tools with explicit trust.

Reviewed for 0.3.2 · Latest documentation

In-process extensions#

A TypeScript or JavaScript module that contributes tools without a process between it and earshot. Put one in .earshot/extensions/ for a project, or in extensions/ under the config directory for every project. Every .ts, .mts, .js or .mjs file there is one extension, named after its file.

// .earshot/extensions/jira.ts
export default {
  tools: [
    {
      name: 'ticket',
      description: 'Reads a Jira ticket by key',
      inputSchema: { type: 'object', properties: { key: { type: 'string' } }, required: ['key'] },
      readOnly: true,
      async execute(input) {
        return `ticket ${input.key}`;
      },
    },
  ],
};

No import is needed and none is offered: earshot ships as a bundled binary, not as a library, so a contract that required importing it would only work where earshot happened to be resolvable. The shape is structural and validated on load. execute may return a string or { output, isError?, title? }.

An extension is not sandboxed and cannot be. Being in-process is the whole point of it — that is what an MCP server is for otherwise. So the boundary is not what it may do once loaded, but whether it is loaded at all: an extension checked into a repository you cloned stays inert until earshot extensions trust <name> names it, recorded in .earshot/settings.local.json, which is not committed. One under your own config directory is your own file and loads.

Its tools are namespaced and gated. jira.ts contributing ticket becomes jira__ticket, so an extension cannot shadow bash. A tool that is not readOnly goes through the permission gate as Extension(jira__ticket); an extension may write the title and detail the prompt shows, but not the rule name or target, or it could match a rule you wrote for something else. Unlike an MCP server, a readOnly claim here is honoured — a false one is the least of what code you already imported into the process could do.

A module that throws on import, or that does not default-export an object, costs that extension's tools and nothing else; earshot extensions list names what is there without importing anything untrusted.

Something unclear?Suggest a correction on GitHub ↗