From 1be7a93d522d9809cb4207879d3f5b7a63fde0a7 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Wed, 6 May 2026 09:52:47 -0700 Subject: [PATCH] fix(vscode): add alpaca mcp auth diagnostics --- scripts/mcp/.env.example | 1 + scripts/mcp/alpaca-mcp-server.sh | 123 +++++++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 6 deletions(-) mode change 100644 => 100755 scripts/mcp/alpaca-mcp-server.sh diff --git a/scripts/mcp/.env.example b/scripts/mcp/.env.example index 0b579a8..0fcbdc9 100644 --- a/scripts/mcp/.env.example +++ b/scripts/mcp/.env.example @@ -5,4 +5,5 @@ ALPACA_API_KEY=your_alpaca_api_key ALPACA_SECRET_KEY=your_alpaca_secret_key # Optional: set to true for paper trading if supported by the installed server version. +# Use false only with live-trading API keys. ALPACA_PAPER_TRADE=true diff --git a/scripts/mcp/alpaca-mcp-server.sh b/scripts/mcp/alpaca-mcp-server.sh old mode 100644 new mode 100755 index a4bbea8..2cc7ec6 --- a/scripts/mcp/alpaca-mcp-server.sh +++ b/scripts/mcp/alpaca-mcp-server.sh @@ -4,11 +4,106 @@ set -eu SCRIPT_DIR="$(CDPATH= cd "$(dirname "$0")" && pwd -P)" ENV_FILE="${ALPACA_MCP_ENV_FILE:-${SCRIPT_DIR}/.env}" -if [ -f "${ENV_FILE}" ]; then - set -a - . "${ENV_FILE}" - set +a -fi +env_has_credentials() { + python3 - "${ENV_FILE}" <<'PY' +import os +import sys + +env_file = sys.argv[1] +values = dict(os.environ) + +if os.path.exists(env_file): + with open(env_file, "r", encoding="utf-8-sig") as handle: + for raw_line in handle: + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if line.startswith("export "): + line = line[7:].strip() + if "=" not in line: + continue + key, value = line.split("=", 1) + values[key.strip()] = value.strip().strip("'\"") + +api_key = values.get("ALPACA_API_KEY", "") +secret_key = values.get("ALPACA_SECRET_KEY", "") +placeholders = {"your_alpaca_api_key", "your_alpaca_secret_key"} +has_real_values = api_key and secret_key and api_key not in placeholders and secret_key not in placeholders +sys.exit(0 if has_real_values else 1) +PY +} + +run_account_check() { + python3 - "${ENV_FILE}" <<'PY' +import json +import os +import sys +import urllib.error +import urllib.request + +env_file = sys.argv[1] + +def load_env(path): + values = {} + if not os.path.exists(path): + return values + with open(path, "r", encoding="utf-8-sig") as handle: + for raw_line in handle: + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if line.startswith("export "): + line = line[7:].strip() + if "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip("'\"") + values[key] = value + return values + +values = {**os.environ, **load_env(env_file)} +api_key = values.get("ALPACA_API_KEY", "") +secret_key = values.get("ALPACA_SECRET_KEY", "") +paper = values.get("ALPACA_PAPER_TRADE", "true").strip().lower() != "false" +placeholders = {"your_alpaca_api_key", "your_alpaca_secret_key"} + +if not api_key or not secret_key or api_key in placeholders or secret_key in placeholders: + print("error: ALPACA_API_KEY and ALPACA_SECRET_KEY must both be set") + sys.exit(2) + +base_url = "https://paper-api.alpaca.markets" if paper else "https://api.alpaca.markets" +request = urllib.request.Request( + f"{base_url}/v2/account", + headers={ + "APCA-API-KEY-ID": api_key, + "APCA-API-SECRET-KEY": secret_key, + "Accept": "application/json", + }, +) + +try: + with urllib.request.urlopen(request, timeout=15) as response: + data = json.loads(response.read().decode("utf-8")) +except urllib.error.HTTPError as error: + body = error.read().decode("utf-8", "replace").strip() + print(f"error: Alpaca account check failed with HTTP {error.code} against {base_url}") + if error.code in (401, 403): + print("hint: verify the key and secret are from the same Alpaca environment and match ALPACA_PAPER_TRADE") + if body: + print(f"details: {body[:300]}") + sys.exit(1) +except Exception as error: + print(f"error: Alpaca account check could not reach {base_url}: {error}") + sys.exit(1) + +status = data.get("status", "unknown") +currency = data.get("currency", "unknown") +trading_blocked = data.get("trading_blocked", "unknown") +print(f"ok: Alpaca account authenticated against {base_url}") +print(f"ok: account status={status} currency={currency} trading_blocked={trading_blocked}") +PY +} if [ "${1:-}" = "--doctor" ]; then if [ -f "${ENV_FILE}" ]; then @@ -17,12 +112,14 @@ if [ "${1:-}" = "--doctor" ]; then echo "warn: env file not found (${ENV_FILE}); copy scripts/mcp/.env.example to scripts/mcp/.env" fi - if [ -n "${ALPACA_API_KEY:-}" ] && [ -n "${ALPACA_SECRET_KEY:-}" ]; then + if env_has_credentials; then echo "ok: Alpaca credential variables are set" else echo "warn: ALPACA_API_KEY and/or ALPACA_SECRET_KEY are not set" fi + echo "hint: run sh scripts/mcp/alpaca-mcp-server.sh --check-account to validate the key pair with Alpaca" + if command -v uvx >/dev/null 2>&1; then echo "ok: uvx available ($(command -v uvx))" exit 0 @@ -39,15 +136,29 @@ if [ "${1:-}" = "--doctor" ]; then exit 127 fi +if [ "${1:-}" = "--check-account" ]; then + run_account_check + exit $? +fi + if command -v uvx >/dev/null 2>&1; then + if [ -f "${ENV_FILE}" ]; then + exec uvx alpaca-mcp-server --env-file "${ENV_FILE}" "$@" + fi exec uvx alpaca-mcp-server "$@" fi if command -v uv >/dev/null 2>&1; then + if [ -f "${ENV_FILE}" ]; then + exec uv tool run alpaca-mcp-server --env-file "${ENV_FILE}" "$@" + fi exec uv tool run alpaca-mcp-server "$@" fi if command -v pipx >/dev/null 2>&1; then + if [ -f "${ENV_FILE}" ]; then + exec pipx run alpaca-mcp-server --env-file "${ENV_FILE}" "$@" + fi exec pipx run alpaca-mcp-server "$@" fi