#!/bin/sh # Outreach2day MCP installer for macOS and Linux. # # curl -fsSL https://outreach2day.com/install | sh # curl -fsSL https://outreach2day.com/install | sh -s -- --client cursor --dry-run # # Read it before you run it: curl -fsSL https://outreach2day.com/install | less # # What this file does: # 1. Looks for Node.js 18 or newer (node on your PATH). # 2. Writes the installer at the bottom of this file (plain JavaScript, between the O2D_INSTALLER_JS markers) to a # temp file and runs it with node. It adds the MCP server "outreach2day" (https://public.outreach2day.com/mcp) # to Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Gemini CLI and Codex CLI when it finds them, # backing up each config file first and keeping every other server in it. It is the same code as # `npx -y outreach2day-mcp` (packages/mcp-installer/install.mjs in the site repo). # 3. Without Node it can still set up Claude Code (claude mcp add). The other clients need Node, or the manual # steps at https://outreach2day.com/mcp. # Nothing else is downloaded. The API key is read from the terminal without echo and is sent only to # public.outreach2day.com. Options: --client , --key (or O2D_API_KEY), --dry-run, --uninstall, --yes, # --no-verify, --help. # # Generated by packages/mcp-installer/build.mjs from wrappers/install.sh + install.mjs. Edit those, not public/install. set -u MCP_URL="https://public.outreach2day.com/mcp" API_KEYS_URL="https://app.outreach2day.com/login?returnTo=%2Fapi-keys" DOCS_URL="https://outreach2day.com/mcp" node_ok() { command -v node >/dev/null 2>&1 && node -e 'process.exit(+process.versions.node.split(".")[0] >= 18 ? 0 : 1)' >/dev/null 2>&1 } # A terminal to ask questions on, even when this script itself arrives through a pipe (curl | sh). has_tty() { (exec /dev/null } main() { if node_ok; then dir=$(mktemp -d "${TMPDIR:-/tmp}/outreach2day-install.XXXXXX") || exit 1 trap 'rm -rf "$dir"' EXIT trap 'rm -rf "$dir"; exit 130' INT TERM installer_js >"$dir/install.mjs" if has_tty; then O2D_INSTALLER_WRAPPED=1 node "$dir/install.mjs" "$@" , --uninstall, --dry-run, --no-verify. Full options need Node.js 18+."; exit 0 ;; *) echo "Option $1 needs Node.js 18+."; exit 2 ;; esac shift done case ",$clients," in ",,") ;; *,claude-code,* | *,claude,* | *,all,*) ;; *) echo "The clients you picked ($clients) need Node.js 18+ for this installer."; exit 1 ;; esac if ! command -v claude >/dev/null 2>&1; then echo "Claude Code (the claude command) was not found either. Install Node.js 18+ and run this again, or follow $DOCS_URL" exit 1 fi if [ -n "$uninstall" ]; then if [ -n "$dry" ]; then echo "Would run: claude mcp remove outreach2day -s user"; exit 0; fi claude mcp remove outreach2day -s user && echo "Removed outreach2day from Claude Code." exit $? fi if [ -z "$key" ]; then if ! has_tty; then echo "No API key and no terminal to ask for one. Create a key at $API_KEYS_URL, then run:" echo " curl -fsSL https://outreach2day.com/install | O2D_API_KEY=ot2d_... sh" exit 2 fi echo "You need an Outreach2day API key. Create one here: $API_KEYS_URL" printf "Paste your API key (input is hidden): " >/dev/tty trap 'stty echo /dev/null; exit 130' INT TERM stty -echo /dev/null IFS= read -r key /dev/null printf "\n" >/dev/tty fi key=$(printf '%s' "$key" | tr -d '[:space:]') key="${key#Bearer}" case "$key" in "" | *[!A-Za-z0-9_-]*) echo "That does not look like an API key (it should start with ot2d_). Nothing changed."; exit 2 ;; esac if [ ${#key} -lt 8 ]; then echo "That API key is too short. Nothing changed."; exit 2; fi if [ -n "$dry" ]; then echo "Would run: claude mcp add --transport http -s user outreach2day $MCP_URL --header \"Authorization: Bearer ot2d_…(hidden)\"" exit 0 fi claude mcp remove outreach2day -s user >/dev/null 2>&1 || true if claude mcp add --transport http -s user outreach2day "$MCP_URL" --header "Authorization: Bearer $key" >/dev/null 2>&1; then echo "Claude Code: added outreach2day (user scope). The key is stored in Claude Code's config (~/.claude.json)." else echo "Claude Code: claude mcp add failed. Add it by hand: $DOCS_URL/claude" exit 1 fi if [ -n "$verify" ] && command -v curl >/dev/null 2>&1; then # The header comes from stdin (-H @-), so the key is not in the process list. code=$(printf 'Authorization: Bearer %s\n' "$key" | curl -s -o /dev/null -w '%{http_code}' -X POST "$MCP_URL" \ -H @- -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"outreach2day-installer","version":"sh"}}}' 2>/dev/null) case "$code" in 200) echo "Server check: $MCP_URL answered." ;; 404) echo "Server check: the Outreach2day MCP server is not yet enabled for your workspace." echo " The config is in place, so Claude Code connects as soon as it opens. You'll be notified; no need to run this again." ;; 401 | 403) echo "Server check: the server rejected this key (HTTP $code). Create a new key at $API_KEYS_URL" ;; *) echo "Server check: no answer from $MCP_URL (HTTP $code). The config is written; try again in a minute." ;; esac fi echo echo "Next: start a new Claude Code session, run /mcp, then ask: \"Check my Outreach2day account status\"" echo "Undo: curl -fsSL https://outreach2day.com/install | sh -s -- --uninstall" } installer_js() { cat <<'O2D_INSTALLER_JS' #!/usr/bin/env node // Outreach2day MCP installer. // // Adds the hosted Outreach2day MCP server (https://public.outreach2day.com/mcp, Streamable HTTP, API key in an // Authorization header) to the MCP clients on this machine, under the server name "outreach2day". // // The same file runs behind every entry point: // curl -fsSL https://outreach2day.com/install | sh public/install (POSIX sh wrapper, runs this with node) // irm https://outreach2day.com/install.ps1 | iex public/install.ps1 (PowerShell wrapper) // npx -y outreach2day-mcp npm package outreach2day-mcp (packages/mcp-installer) // // Per client (config formats from each client's docs, checked 28.09.2026): // Claude Code `claude mcp add --transport http -s user outreach2day --header "Authorization: Bearer "` // (the key ends up in ~/.claude.json, Claude Code's own config) // Claude Desktop claude_desktop_config.json, mcpServers.outreach2day runs the mcp-remote bridge through npx; // the key is in its env block (Claude Desktop takes remote servers in this file only via a bridge) // Cursor ~/.cursor/mcp.json, mcpServers.outreach2day = { url, headers } // VS Code /Code/User/mcp.json, servers.outreach2day = { type: "http", url, headers } // Windsurf ~/.codeium/windsurf/mcp_config.json, mcpServers.outreach2day = { serverUrl, headers } // Gemini CLI ~/.gemini/settings.json, mcpServers.outreach2day = { httpUrl, headers } with the header read // from $OUTREACH2DAY_API_KEY (Gemini CLI expands ${VAR} in settings.json) // Codex CLI ~/.codex/config.toml, [mcp_servers.outreach2day] url + bearer_token_env_var = "OUTREACH2DAY_API_KEY" // For Gemini CLI and Codex the key goes to ~/.config/outreach2day/env (0600), loaded by your shell profile // (Windows: a user environment variable). // // Safety rules this file keeps: // - The server URL is pinned to https://public.outreach2day.com/mcp (a local http://127.0.0.1 / localhost server is // allowed for testing only). The key is sent only to public.outreach2day.com. // - The key is never printed: every line of output goes through redact(). // - A config file is backed up next to itself (.outreach2day-backup-