#!/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 # 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. # No API key by default: each client signs in to Outreach2day (OAuth) on first use. API key mode (headless / CI, # clients without OAuth): --use-key (asked in the terminal without echo) or --key / O2D_API_KEY; the key is # sent only to public.outreach2day.com. Options: --client , --use-key, --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" ] && [ -z "$use_key" ]; then # OAuth: the URL only. Claude Code asks you to sign in (/mcp → Authenticate). if [ -n "$dry" ]; then echo "Would run: claude mcp add --transport http -s user outreach2day $MCP_URL"; 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" >/dev/null 2>&1; then echo "Claude Code: added outreach2day (user scope). No API key needed: you sign in to Outreach2day from Claude Code." else echo "Claude Code: claude mcp add failed. Add it by hand: $DOCS_URL/claude" exit 1 fi echo echo "Next: start Claude Code, run /mcp, pick outreach2day, choose Authenticate (or run: claude mcp login outreach2day)." echo "Then ask: \"Check my Outreach2day account status\"" echo "Headless or CI use: run again with --use-key. Undo: curl -fsSL https://outreach2day.com/install | sh -s -- --uninstall" exit 0 fi if [ -z "$key" ]; then if ! has_tty; then echo "--use-key needs a terminal to ask for the key. 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 "API key mode. Create a key 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 # The config names $OUTREACH2DAY_API_KEY (Claude Code expands ${VAR} in headers); the key itself goes only to # ~/.config/outreach2day/env (0600), loaded from your shell profile. Same files as the Node installer. env_file="${XDG_CONFIG_HOME:-$HOME/.config}/outreach2day/env" case "$(basename "${SHELL:-sh}")" in zsh) profile="${ZDOTDIR:-$HOME}/.zshrc" ;; bash) profile="$HOME/.bashrc" ;; *) profile="$HOME/.profile" ;; esac if [ -n "$dry" ]; then echo "Would write: $env_file (0600, the key) and a line in $profile that loads it" echo "Would run: claude mcp add --transport http -s user outreach2day $MCP_URL --header 'Authorization: Bearer \${OUTREACH2DAY_API_KEY}'" exit 0 fi mkdir -p "$(dirname "$env_file")" || exit 1 (umask 077 && printf "# Outreach2day MCP API key, written by https://outreach2day.com/install (--use-key / --key).\nexport OUTREACH2DAY_API_KEY='%s'\n" "$key" >"$env_file") || exit 1 chmod 600 "$env_file" if ! grep -q '# >>> outreach2day mcp >>>' "$profile" 2>/dev/null; then printf '\n# >>> outreach2day mcp >>>\n[ -f "%s" ] && . "%s"\n# <<< outreach2day mcp <<<\n' "$env_file" "$env_file" >>"$profile" fi echo "API key: saved to $env_file (readable only by you), loaded from $profile." 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 ${OUTREACH2DAY_API_KEY}' >/dev/null 2>&1; then echo "Claude Code: added outreach2day (user scope). Its config names OUTREACH2DAY_API_KEY; the key itself is not in it." 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: $MCP_URL did not answer (HTTP 404)." echo " The config is in place. Try again in a few minutes or write to support@outreach2day.com." ;; 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: open a new terminal (it loads OUTREACH2DAY_API_KEY), start Claude Code, run /mcp, then ask: \"Check my Outreach2day account status\"" echo "Undo: curl -fsSL https://outreach2day.com/install | sh -s -- --uninstall (with Node.js it also removes $env_file)" } 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) 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) // node install.mjs from a checkout (the npm package outreach2day-mcp is not // published: `npx -y outreach2day-mcp` returns 404) // // Default: OAuth. No API key is asked for or stored. Each client gets the server URL only and signs in to // Outreach2day itself on first use (the server answers an anonymous request with 401 + WWW-Authenticate // resource_metadata; OAuth 2.1 with PKCE and dynamic client registration). Per client (checked 29.09.2026): // Claude Code `claude mcp add --transport http -s user outreach2day `; sign in: /mcp → Authenticate, or // `claude mcp login outreach2day` https://code.claude.com/docs/en/mcp // Claude Desktop nothing is written: remote servers are custom connectors (Customize → Connectors → Add custom // connector), claude_desktop_config.json is for local servers. The installer prints the Add to Claude // link (CLAUDE_CONNECT_URL), which opens that dialog with the name and URL filled in // https://support.claude.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp // Cursor ~/.cursor/mcp.json, mcpServers.outreach2day = { url } https://cursor.com/docs/context/mcp // VS Code /Code/User/mcp.json, servers.outreach2day = { type: "http", url } // (MCP authorization with dynamic client registration) https://code.visualstudio.com/updates/v1_101 // Windsurf now Devin Desktop: $XDG_CONFIG_HOME/devin/mcp_config.json or ~/.config/devin/mcp_config.json // (Windows: %APPDATA%\devin\mcp_config.json), mcpServers.outreach2day = { serverUrl }; also // ~/.codeium/windsurf/mcp_config.json when that directory exists (older Windsurf builds) // https://docs.devin.ai/desktop/cascade/mcp // Gemini CLI ~/.gemini/settings.json, mcpServers.outreach2day = { httpUrl }; sign in: /mcp auth outreach2day // https://geminicli.com/docs/tools/mcp-server/ // Codex CLI ~/.codex/config.toml, [mcp_servers.outreach2day] url; sign in: codex mcp login outreach2day // https://learn.chatgpt.com/docs/extend/mcp?surface=cli // // API key mode (--key, --use-key or O2D_API_KEY): for headless / CI use and clients without OAuth. The key is written // to one place only, ~/.config/outreach2day/env (0600, loaded by your shell profile; Windows: a user environment // variable). The client configs name the variable and never hold the key: // Claude Code --header "Authorization: Bearer ${OUTREACH2DAY_API_KEY}" (Claude Code expands ${VAR} in headers) // Cursor, VS Code, Windsurf headers.Authorization = "Bearer ${env:OUTREACH2DAY_API_KEY}" // Gemini CLI headers.Authorization = "Bearer ${OUTREACH2DAY_API_KEY}" // Codex CLI bearer_token_env_var = "OUTREACH2DAY_API_KEY" // Claude Desktop no key mode: the connector signs in. An mcp-remote entry from 0.1.0 is left alone until --uninstall. // Running again without a key switches the entries back to OAuth and removes the key file once nothing reads it. // // 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). A key is sent only to public.outreach2day.com. // - A key is never printed: every line of output goes through redact(). // - A config file is backed up next to itself (.outreach2day-backup-