0075: Assistants connect by their own commands

On this page

Status: accepted

Context

Settings › Coding assistants (MCP) only showed a command and a JSON snippet to copy. One click is easier, but each assistant keeps its MCP servers in its own settings files, and Short Leash never reads or writes another tool's files (invariant 3, 0005). Those files also change shape between versions, and the assistant has its own rules for trusting a server. The same block lets assistants and people create and drop databases from the terminal and over MCP, so a mistaken drop needs a way back.

Decision

  • Each assistant's own entry point, nothing else. Short Leash adds its server through the command or link the assistant documents for that purpose:
    • Claude Code: claude mcp add --scope user leash -- <leash> mcp. User scope makes Short Leash available in every project; the default local scope would add it to one folder only.
    • VS Code on macOS, when code is on the PATH: code --add-mcp '{"name":"leash","command":"<leash>","args":["mcp"]}', which adds the server to the user profile.
    • VS Code otherwise (on Windows, or with the app but no code command): the install link vscode:mcp/install?<JSON>, the same object as --add-mcp, percent-encoded. On Windows code is a batch file, and a JSON argument can't be passed through one safely.
    • Cursor: the install link cursor://anysphere.cursor-deeplink/mcp/install?name=leash&config=<config>, where the config is {"command":"<leash>","args":["mcp"]}, base64-encoded and then percent-encoded. We use the app's own scheme rather than the cursor.com/install-mcp web page, so opening it stays on this computer.
    • The page asks the app to open an assistant's link by naming only the assistant. The app builds the link itself from Short Leash's own leash path, checks that it starts with that assistant's install prefix (vscode:mcp/install? or cursor://anysphere.cursor-deeplink/mcp/install?), and opens it with the opener plugin's Rust API. The page's own openUrl keeps the plugin's defaults (http, https, mailto, tel), so a link from outside Short Leash, such as a Pair host's link to its site or a call collection's OAuth address, can't open an assistant's install prompt; both of those are also refused unless they're http or https. Opening a link only hands it to the assistant, which decides what happens next, including asking before it installs anything; Short Leash can't see or skip that step.
  • Finding assistants without opening their files. leashd checks only whether claude or code is on the login shell's PATH (runtimes::apps::login_shell_path, so nvm and Homebrew installs are found) and whether the app's bundle or program exists in the usual app folders. Assistants it doesn't find get no button, and the snippets to copy stay under "Other tools".
  • The full path to Short Leash's own leash. The command and links always carry an absolute path, because apps opened from the Dock or Start menu don't read the shell's PATH. On macOS it's leash in Short Leash's bin folder, which stays the same across updates. On Windows it's leash.exe beside leashd.exe in the app's folder, and bin\leash.cmd only if that's missing, because an assistant can't always start a .cmd file without a shell.
  • Run as the user, with a limit. The commands run as the user, with PATH set to the login shell's, kill_on_drop, and a 30-second limit (defaults::ASSISTANT_ADD_TIMEOUT). Output saying the server already exists counts as done ("Short Leash is already in Claude Code"). Nothing runs as root.
  • A backup before every drop. leash db drop, POST /v1/databases/drop and the drop_database tool first take a Before drop backup with the server's own dump tool (0021), named <time>-before-drop.<ext> beside the service's other backups, and drop only if it succeeded. Server databases (mysql, postgres, sys and the like) can't be dropped. POST /v1/services/{id}/remove, which the remove_service tool calls, stops and removes an added service but always keeps its data on disk; deleting the data stays with the app and leash service remove --delete-data.
  • Chat never drops or removes. drop_database and remove_service are NeverInChat; create_database and add_service are Act, so chat asks before each one (0062).

These entry points were checked against each assistant's own help and docs on 2026-10-19.

Consequences

  • One click per assistant, and the assistant stays in charge of its settings, including trusting and removing Short Leash. Short Leash can't tell whether a link was accepted.
  • A new assistant needs a documented entry point of its own. One without a command or install link stays copy-and-paste.
  • The commands and link formats belong to the assistants and can change. The buttons are checked against their current versions before each release, and a failed command shows the assistant's own output.
  • On Windows claude is often a batch file, which Rust starts only with arguments it can quote safely. If that fails on Windows, Claude Code stays copy-and-paste there.
  • A drop takes as long as a dump of that database. Because retention never deletes a database's newest backup, the Before drop backup of a dropped database stays until the person deletes it in Backups, so dropped databases add up on disk. A drop that can't be backed up doesn't happen; the person can free space or drop the database with the server's own client.