MCP support
Loach speaks the Model Context Protocol (MCP) over two transports: Streamable HTTP for hosted servers and gateways, and local process (stdio) for the many servers published as a command to run. Add servers in Settings → MCP and their tools become available to the model during a chat - and by default, every call waits for your approval.
What is MCP?
MCP is an open protocol that lets models call structured tools served by a separate process. Where Web fetch just hands the model a page of text, an MCP server can expose typed tools (“list files in this repo”, “search this database”, “send this email”) that the model calls with arguments and gets typed results back. The integration is provider-agnostic from Loach’s point of view - whichever model you are chatting with can use the tools, as long as it supports tool calls.
Adding a server
Open Settings → MCP and click Add server. Every server has:
- Display name - the label shown in menus and in the approval card. One line, at most 100 characters.
- Transport - Streamable HTTP or Local process (stdio); the fields below it change to match.
- Ask before each tool call - the per-call approval switch, on by default (see Approving tool calls).
- Enabled - disable a server without deleting it. The same switch sits on the server’s row in the list, next to the delete button, which asks before removing anything. A server saved switched off isn’t looked up, so you can rename or disable one whose host doesn’t resolve right now (offline, off the VPN); switching it on checks the address again.
- Test connection - runs the
initialize+tools/listhandshake without saving, and reports the server’s name and version, the protocol version and its tool list. Use this before saving.
Streamable HTTP
- URL - the server’s
http(s)://…endpoint, where requests are POSTed.localhostand private LAN addresses are accepted, since self-hosted servers commonly live there; only link-local (cloud-metadata) addresses are refused. The legacy two-endpoint SSE transport is not supported. - Headers - optional, one
Name: valueper line, typicallyAuthorization: Bearer …. Up to 16. When you open a server that already has headers, the field is masked; its eye button (Show values / Hide values) reveals it.
Local process (stdio)
- Command - a program on your
PATH(npx,uvx,node) or a full path to one. - Arguments - one per line, with no shell quoting: each line is passed as one argument, spaces included; only the ends of a line are trimmed.
- Environment variables - optional
NAME=valuelines layered over Loach’s own environment. This is where most servers take their API keys, so the field is masked like Headers.
Loach starts the program in your home folder - so a relative path in its arguments means the same thing however Loach was launched - and talks to it over its standard input and output. Saving doesn’t start it: it first runs when you test it, list tools with /tools, or send a message while it is enabled, and then stays running between turns. When you disable, edit or delete the server, Loach closes its input and gives it two seconds to exit; one that doesn’t is stopped along with every process it started (such as the node behind npx). When Loach quits, idle servers have their input closed and share one second to exit before they are stopped; one in the middle of a call is stopped at once. If the program exits before it answers, Test connection quotes the last lines of its error output (npm ERR! 404, a Python traceback), which usually says why.
On Windows, a bare npx resolves to npx.cmd through Loach’s own PATH, so commands work as typed; a PATH set in the server’s environment isn’t used for that lookup. On macOS and Linux the server gets the PATH your login shell builds - the one a terminal sees - followed by Loach’s own, so tools installed through Homebrew, nvm or ~/.local/bin are found even when Loach was started from the Dock or an app menu. A PATH set in the server’s environment replaces both.
Consent before a program runs
A stdio server runs a program on your computer with your permissions, so Loach asks first in a native system dialog, “Run MCP server …?”. It appears when you test a stdio server, save a new one switched on, change the command, arguments or environment of an enabled one, or turn a disabled one on. Saving a server switched off doesn’t ask; turning it on does.
- What it shows - the program and each argument on a line of its own (quoted when it contains spaces), and the environment variables with their values - except variables whose names say they hold a credential (
…TOKEN,…_KEY,…SECRET,…PASSWORD,…AUTH…and the like), which read value hidden. Variables that change what runs (PATH,NODE_OPTIONS,PYTHONPATH,LD_PRELOAD, package-index settings and the like) are always shown in full. On Windows, when a bare command such asnpxis found onPATH, it also names the file that will run. - What it refuses - a command line too long to show in full (over 1,500 characters) rather than cutting it short, and control characters or invisible and text-reordering Unicode anywhere in the name, command, arguments or environment.
- Cancel is the default - pressing Enter declines; only clicking Start server starts the program. The dialog comes from Loach’s native side rather than the app window, so nothing inside the window can click it for you.
- Once per session - an approved command line is remembered until you quit, so a test followed by a save asks once. After a restart, an enabled server you already approved starts without asking, though Test connection asks again.
- Auto-approve is part of it - when Ask before each tool call is off, the dialog says so: agreeing to run the program then also means its tools run unasked. Turning it off later, on a server you already approved, doesn’t ask again.
Approving tool calls
With Ask before each tool call on, every call from that server pauses the reply and shows a card - “Allow server to run tool?” - with the arguments the model wants to pass, and three answers:
- Allow once - run it this time.
- Always allow tool - run it, and stop asking for this tool on this server. The choice is saved with the server and listed in its editor under Always allowed, where Ask again for all clears the list once you save. Saving the server with a different URL, command, arguments or environment clears it too - the answer was about that program or endpoint.
- Deny - the tool does not run. The model is told you declined and continues without the result.
A card left unanswered for 10 minutes doesn’t run either: the call is marked No answer, and the model is told nobody answered - not that you declined. Stop cancels the reply as usual. While a card waits, replies in other chats queue behind it; if it’s in a chat you aren’t looking at, a notice names that chat with an Open button, and a queued chat shows what it is waiting for with a Go to it button.
Turn Ask before each tool call off for a server you trust and its tools run without a card; the server’s row then carries an Auto-approve badge so the exception stays visible. Built-in tools never ask, except the file tools that change files - see Work in folders.
In a chat
The calls render in the collapsible “Called N tools” block above the answer - expand it to inspect each call’s name, arguments and result. The same block also covers built-in tools. When a tool returns an image, Loach shows it as an inline attachment on the reply rather than dropping it; the model itself is only told an image was attached.
- Result size - the model gets at most 32 KB of a result, with a note saying it was cut; the block shows up to 256 KB.
- Rounds - one reply gets at most 10 rounds with the model, the last without tools. See “Stopped after 10 tool-use turns”.
- Unreachable servers - if a server can’t be reached, the reply opens with a notice naming it and carries on with the other servers’ tools. Errors in a chat stop short of a stdio server’s own error output, which can print anything, keys included - the chat’s text goes to the model and into exports. Test connection shows it.
/tools- lists the tools each enabled server exposes, or why it couldn’t list them.- Caching - the tool list is reused for up to five minutes between messages. Adding, editing, deleting or toggling a server refreshes it at once.
Safety guards
- HTTP - URLs are validated and the resolved addresses pinned for the connection, header names and values are checked for disallowed characters and size, responses are capped at 4 MiB and each request has a 30-second timeout, so a hung or hostile server can’t freeze the chat or exhaust the app. Loach connects directly, never through a proxy, and doesn’t follow redirects: when a server answers with one, the error says where it points without quoting its path - usually it’s the same URL with or without a trailing
/, and the error says which to change. Connection errors leave the URL out, since it can carry a credential. - stdio - startup gets 60 seconds (first runs of
npx -y …download the package) and each request 30 seconds. A request that runs out of time fails, but the server keeps running - it may still be working - and is told the result is no longer wanted, as it is when you press Stop mid-call. A server that leaves two requests in a row unanswered, or stops reading its input, counts as hung: Loach stops it, and the next call starts it afresh. A single reply over 4 MiB, or one that isn’t a valid JSON-RPC response, fails that call only. - Long-running tools - on either transport, a tool call whose server reports progress gets a fresh 30 seconds with each report, up to 10 minutes in all.
- Error replies - a server that answers a call with an error (bad arguments, an unknown tool) fails just that call; the server keeps running and its session stays open.
- Tool catalogue - up to 200 tools per server, read across up to 10 pages when the server hands out its list a page at a time; descriptions are cut at 2,000 characters; a tool whose input schema isn’t a JSON object under 16 KB is skipped; and tool names are reduced to the characters model APIs accept. A name that runs past 64 characters together with its server’s prefix is shortened and ends in a short hash, so it still fits and stays unique. If two of one server’s tools end up with the same name, only the first is kept, so a chat never sees colliding entries from the same source.
- Disabled means disabled - a server switched off never runs, even for a call the model asked for before you switched it off.
Backups and imports
Headers and environment variables hold API keys, so they are left out of backups. Arguments are exported as typed, except credentials, which read REDACTED:
- In URLs - a password or a bare token before the host (
postgresql://user:…@host,https://ghp_…@github.com) and a query value whose name marks a secret (?api_key=…). - In flags - the value of a flag such as
--token,--api-key=…or--auth. Only the whole wordauthcounts, so--authorkeeps its value. - Assignments and headers - a
NAME=valueorName: valueargument whose name marks a secret, such as-e GITHUB_TOKEN=…or--header "Authorization: …".
An HTTP server’s URL loses anything before the host and the value of every query parameter. Its path is kept, so a secret inside the path itself isn’t redacted. An environment variable or a header is still the better place for a secret.
A backup can’t prove you set up a command on this machine, so imported stdio servers arrive disabled; turning one on raises the consent dialog. HTTP servers keep the on/off state they had in the backup, unless their host couldn’t be looked up during the import - then they arrive switched off too. A server whose name breaks the naming rules (over 100 characters, or with invisible characters) is renamed rather than failing the import: the bad characters are dropped, and a name already taken gets “(2)”. After an import, re-enter any header, variable or redacted value a server needs.
Private Chat deliberately does not expose MCP tools - a tool call inside that surface could side-channel the conversation out to a third party, which would defeat the “leaves no trace” promise. It leaves out the built-in tools too, so it offers no tools at all.
Loach offers the tools to whichever model you are chatting with; the model has to support tool calling to use them. Hosted models from major providers typically do, and many local Ollama models do too - Ollama refuses a request to one that doesn’t with an error saying it does not support tools.