Merge pull request #1 from kacperkwapisz/feat/poke-login-sdk
feat: integrate Poke login SDK token automation into start.sh
This commit is contained in:
@@ -13,18 +13,46 @@ An MCP server that bridges IMAP/SMTP email accounts to [Poke](https://poke.com).
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Copy this prompt into your AI coding agent (Claude Code, Cursor, etc.):
|
**Prerequisites:** Python 3.10+ and Node.js 18+ (which includes `npx` and `npm`).
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
Set up poke-mail (https://github.com/kacperkwapisz/poke-mail) for me — clone the repo, create a Python virtualenv named .venv, install requirements.txt, copy config.example.yml to config.yml, then help me configure config.yml — you can guide me on which IMAP/SMTP host and port to use for my email provider (iCloud, Gmail, Outlook, etc.) and what username format to use, but do NOT type or suggest passwords or API keys, tell me to enter those myself outside of this terminal and confirm when done — then generate a random 32+ character MCP_API_KEY, save it to a .env file as MCP_API_KEY=<the-key>, install the poke npm package globally, run poke login so I can authenticate, and run start.sh to start the server and tunnel it to Poke.
|
git clone https://github.com/kacperkwapisz/poke-mail.git
|
||||||
|
cd poke-mail
|
||||||
```
|
```
|
||||||
|
|
||||||
To start the server again later:
|
If you haven't logged into Poke yet, do that first — `start.sh` will pick up your token automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx poke login
|
||||||
|
```
|
||||||
|
|
||||||
|
Then just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./start.sh
|
./start.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
On the **first run**, `start.sh` automatically handles the full setup:
|
||||||
|
1. Creates a Python virtualenv and installs dependencies
|
||||||
|
2. Copies `config.example.yml` → `config.yml`
|
||||||
|
3. Reads your Poke API key from `poke login` credentials and injects it into `config.yml`
|
||||||
|
4. Generates a random `MCP_API_KEY` and saves it to `.env`
|
||||||
|
5. Immediately starts the server and tunnel
|
||||||
|
|
||||||
|
After the first run completes (or if the server exits with email auth errors), open `config.yml` and fill in your email account credentials, then run `./start.sh` again.
|
||||||
|
|
||||||
|
> **Note:** If your email credentials in `config.yml` are still placeholders, IMAP/SMTP connections will fail on startup. Update the file and rerun `./start.sh`.
|
||||||
|
|
||||||
|
On **subsequent runs**, `start.sh` skips setup and goes straight to starting the server and tunnel.
|
||||||
|
|
||||||
|
### AI coding agent setup
|
||||||
|
|
||||||
|
Copy this prompt into your AI coding agent (Claude Code, Cursor, etc.):
|
||||||
|
|
||||||
|
```text
|
||||||
|
Set up poke-mail (https://github.com/kacperkwapisz/poke-mail) for me — clone the repo, run 'npx poke login' so I can authenticate with Poke (wait for me to confirm), then run './start.sh' which will automatically wire up my Poke API key, generate an MCP_API_KEY, set up the virtualenv, and start the server and tunnel — then help me fill in my email credentials in config.yml (guide me on IMAP/SMTP host and port for my provider but do NOT type passwords or secrets — tell me to enter those myself and confirm when done); if the server fails due to missing/invalid email credentials, have me update config.yml and run './start.sh' again to restart it.
|
||||||
|
```
|
||||||
|
|
||||||
## Manual Setup
|
## Manual Setup
|
||||||
|
|
||||||
### 1. Configure accounts
|
### 1. Configure accounts
|
||||||
@@ -33,7 +61,7 @@ To start the server again later:
|
|||||||
cp config.example.yml config.yml
|
cp config.example.yml config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `config.yml` with your email credentials:
|
Edit `config.yml` with your email credentials and Poke API key (from [poke.com/settings/advanced](https://poke.com/settings/advanced)):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
webhook_url: https://poke.com/api/v1/inbound/api-message
|
webhook_url: https://poke.com/api/v1/inbound/api-message
|
||||||
@@ -75,7 +103,7 @@ pip install -r requirements.txt
|
|||||||
### 3. Run
|
### 3. Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
MCP_API_KEY=your-secret-key python src/server.py
|
MCP_API_KEY=your-secret-key python3 src/server.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Test
|
### 4. Test
|
||||||
@@ -90,7 +118,9 @@ Open http://localhost:3000 and connect to `http://localhost:3000/mcp` using "Str
|
|||||||
|
|
||||||
Set `MCP_API_KEY` to secure the server. All requests must include `Authorization: Bearer <MCP_API_KEY>`.
|
Set `MCP_API_KEY` to secure the server. All requests must include `Authorization: Bearer <MCP_API_KEY>`.
|
||||||
|
|
||||||
If `MCP_API_KEY` is not set, the server runs unauthenticated (with a warning). **Always set it in production.**
|
When running via `start.sh` (which uses `poke tunnel`), set `POKE_TUNNEL=1` to make `MCP_API_KEY` optional — the tunnel handles authentication. `start.sh` sets this automatically.
|
||||||
|
|
||||||
|
If `MCP_API_KEY` is not set and `POKE_TUNNEL` is not `1`, the server runs unauthenticated (with a warning). **Always set it in non-tunnel deployments.**
|
||||||
|
|
||||||
When connecting from Poke, add the bearer token in your connection settings.
|
When connecting from Poke, add the bearer token in your connection settings.
|
||||||
|
|
||||||
@@ -146,7 +176,8 @@ The server is mostly idle (IMAP IDLE + lightweight HTTP). Recommended limits for
|
|||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `MCP_API_KEY` | — | **Required in production.** Bearer token to secure the MCP server |
|
| `MCP_API_KEY` | — | Bearer token to secure the MCP server. Optional when `POKE_TUNNEL=1`. |
|
||||||
|
| `POKE_TUNNEL` | `0` | Set to `1` when running behind the poke tunnel — skips `MCP_API_KEY` auth requirement. `start.sh` sets this automatically. |
|
||||||
| `CONFIG_PATH` | `config.yml` | Path to config file |
|
| `CONFIG_PATH` | `config.yml` | Path to config file |
|
||||||
| `POKE_WEBHOOK_URL` | from config | Overrides webhook URL in config |
|
| `POKE_WEBHOOK_URL` | from config | Overrides webhook URL in config |
|
||||||
| `POKE_API_KEY` | from config | Overrides Poke API key in config |
|
| `POKE_API_KEY` | from config | Overrides Poke API key in config |
|
||||||
|
|||||||
Executable → Regular
+17
-3
@@ -601,10 +601,24 @@ async def lifespan(server: FastMCP):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
mcp_api_key = os.environ.get("MCP_API_KEY", "")
|
mcp_api_key = os.environ.get("MCP_API_KEY", "")
|
||||||
auth = ApiKeyAuth(mcp_api_key) if mcp_api_key else None
|
|
||||||
if not mcp_api_key:
|
# When running behind the poke tunnel (POKE_TUNNEL=1), the tunnel handles
|
||||||
|
# authentication so the MCP_API_KEY bearer check is optional.
|
||||||
|
# In direct / Docker deployments the key is still required for security.
|
||||||
|
poke_tunnel_mode = os.environ.get("POKE_TUNNEL", "") == "1"
|
||||||
|
|
||||||
|
if mcp_api_key:
|
||||||
|
auth = ApiKeyAuth(mcp_api_key)
|
||||||
|
elif poke_tunnel_mode:
|
||||||
|
auth = None # tunnel handles auth
|
||||||
|
logger.info(
|
||||||
|
"POKE_TUNNEL=1 detected — MCP_API_KEY not required (tunnel handles auth)."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
auth = None
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"MCP_API_KEY not set — server is unauthenticated. Set MCP_API_KEY to secure it."
|
"MCP_API_KEY not set — server is unauthenticated. "
|
||||||
|
"Set MCP_API_KEY or use POKE_TUNNEL=1 to silence this warning."
|
||||||
)
|
)
|
||||||
|
|
||||||
mcp = FastMCP("poke-mail", lifespan=lifespan, auth=auth)
|
mcp = FastMCP("poke-mail", lifespan=lifespan, auth=auth)
|
||||||
|
|||||||
@@ -3,41 +3,262 @@ set -euo pipefail
|
|||||||
|
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
# Check for updates
|
# ── OTA update ────────────────────────────────────────────────────────────────
|
||||||
REPO="kacperkwapisz/poke-mail"
|
# Uses curl + Python stdlib tarfile — no git or unzip required.
|
||||||
LOCAL_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
#
|
||||||
REMOTE_SHA=$(curl -sf "https://api.github.com/repos/${REPO}/commits/main" \
|
# Flow:
|
||||||
| grep -m1 '"sha"' | cut -d'"' -f4 || echo "")
|
# 1. Fetch latest commit SHA from GitHub API (tiny JSON, ~1 KB, 5 s timeout).
|
||||||
|
# 2. Compare against .poke_version (last installed SHA). Skip if already
|
||||||
|
# up to date or if the remote is unreachable.
|
||||||
|
# 3. Download the repo tarball only when an update exists (30 s timeout).
|
||||||
|
# 4. Extract with Python tarfile, stripping the GitHub top-level prefix and
|
||||||
|
# skipping protected local files (.env, config.yml, .venv).
|
||||||
|
# 5. Persist new SHA to .poke_version and reinstall deps if requirements.txt
|
||||||
|
# changed.
|
||||||
|
|
||||||
if [ -n "$REMOTE_SHA" ] && [ "$REMOTE_SHA" != "$LOCAL_SHA" ]; then
|
if command -v curl &>/dev/null && command -v python3 &>/dev/null; then
|
||||||
echo "⚡ A newer version of poke-mail is available."
|
_OTA_REPO="kacperkwapisz/poke-mail"
|
||||||
echo " Local: ${LOCAL_SHA:0:7}"
|
_OTA_BRANCH="main"
|
||||||
echo " Remote: ${REMOTE_SHA:0:7}"
|
_VERSION_FILE=".poke_version"
|
||||||
echo " Run 'git pull' to update."
|
|
||||||
|
echo "Checking for updates..."
|
||||||
|
|
||||||
|
# Step 1: lightweight SHA check (fail silently if offline)
|
||||||
|
_REMOTE_SHA=$(curl -sf --max-time 5 \
|
||||||
|
"https://api.github.com/repos/${_OTA_REPO}/commits/${_OTA_BRANCH}" \
|
||||||
|
| python3 -c \
|
||||||
|
"import json,sys; print(json.load(sys.stdin)['sha'])" \
|
||||||
|
2>/dev/null || echo "")
|
||||||
|
|
||||||
|
_LOCAL_SHA=$(cat "$_VERSION_FILE" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$_REMOTE_SHA" ]; then
|
||||||
|
echo " ℹ Could not reach remote — continuing with local version."
|
||||||
|
echo ""
|
||||||
|
elif [ "$_REMOTE_SHA" = "$_LOCAL_SHA" ]; then
|
||||||
|
echo " ✓ Already up to date (${_REMOTE_SHA:0:7})"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo " ↳ Update found (${_LOCAL_SHA:0:7:-} → ${_REMOTE_SHA:0:7}), downloading..."
|
||||||
|
|
||||||
|
# Hash requirements.txt before extraction so we can detect changes
|
||||||
|
_REQS_BEFORE=$(python3 -c \
|
||||||
|
"import hashlib; print(hashlib.md5(open('requirements.txt','rb').read()).hexdigest())" \
|
||||||
|
2>/dev/null || echo "")
|
||||||
|
|
||||||
|
_TMP_TAR=$(mktemp /tmp/poke-mail-update.XXXXXX.tar.gz)
|
||||||
|
|
||||||
|
if curl -sfL --max-time 30 \
|
||||||
|
"https://api.github.com/repos/${_OTA_REPO}/tarball/${_OTA_BRANCH}" \
|
||||||
|
-o "$_TMP_TAR" 2>/dev/null; then
|
||||||
|
|
||||||
|
# Extract with Python: strip GitHub's top-level dir, skip protected paths
|
||||||
|
python3 - "$_TMP_TAR" <<'PYEOF'
|
||||||
|
import sys, tarfile, os
|
||||||
|
|
||||||
|
archive = sys.argv[1]
|
||||||
|
# Files/dirs that must never be overwritten by an OTA update
|
||||||
|
PROTECTED = {'.env', 'config.yml', '.venv', '.poke_version'}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with tarfile.open(archive, 'r:gz') as tf:
|
||||||
|
members = tf.getmembers()
|
||||||
|
if not members:
|
||||||
|
sys.exit(0)
|
||||||
|
# GitHub tarball root dir is e.g. "owner-repo-<sha>/"
|
||||||
|
prefix = members[0].name.split('/')[0] + '/'
|
||||||
|
for m in members:
|
||||||
|
if not m.name.startswith(prefix):
|
||||||
|
continue
|
||||||
|
rel = m.name[len(prefix):] # path relative to repo root
|
||||||
|
if not rel: # skip the root dir entry itself
|
||||||
|
continue
|
||||||
|
top = rel.split('/')[0]
|
||||||
|
if top in PROTECTED:
|
||||||
|
continue
|
||||||
|
m.name = rel
|
||||||
|
try:
|
||||||
|
tf.extract(m, path='.', set_attrs=False)
|
||||||
|
except Exception:
|
||||||
|
pass # best-effort; don't abort on permission issues etc.
|
||||||
|
except Exception as e:
|
||||||
|
print(f' ⚠ Extraction error: {e}')
|
||||||
|
sys.exit(1)
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
# Persist new SHA so we don't re-download next run
|
||||||
|
echo "$_REMOTE_SHA" > "$_VERSION_FILE"
|
||||||
|
echo " ✓ Updated to ${_REMOTE_SHA:0:7}"
|
||||||
|
|
||||||
|
# Reinstall deps if requirements.txt changed
|
||||||
|
_REQS_AFTER=$(python3 -c \
|
||||||
|
"import hashlib; print(hashlib.md5(open('requirements.txt','rb').read()).hexdigest())" \
|
||||||
|
2>/dev/null || echo "")
|
||||||
|
if [ -n "$_REQS_BEFORE" ] && [ "$_REQS_BEFORE" != "$_REQS_AFTER" ]; then
|
||||||
|
echo " ↳ requirements.txt changed — reinstalling dependencies..."
|
||||||
|
[ -d .venv ] && source .venv/bin/activate
|
||||||
|
pip install -q -r requirements.txt
|
||||||
|
echo " ✓ Dependencies updated"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ℹ Download failed — continuing with local version."
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$_TMP_TAR"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── One-time setup (skipped on subsequent runs) ───────────────────────────────
|
||||||
|
|
||||||
|
# 1. Python virtualenv + dependencies
|
||||||
|
if [ ! -d .venv ]; then
|
||||||
|
echo "First run — setting up poke-mail..."
|
||||||
|
echo ""
|
||||||
|
echo "Creating Python virtualenv (.venv)..."
|
||||||
|
python3 -m venv .venv
|
||||||
|
fi
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
if ! python3 -c "import fastmcp" &>/dev/null 2>&1; then
|
||||||
|
echo "Installing Python dependencies..."
|
||||||
|
pip install -q -r requirements.txt
|
||||||
|
echo " ✓ Dependencies installed"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Load .env if present
|
# 2. config.yml — copy example if missing
|
||||||
|
if [ ! -f config.yml ]; then
|
||||||
|
echo "Copying config.example.yml → config.yml..."
|
||||||
|
cp config.example.yml config.yml
|
||||||
|
echo " ✓ config.yml created"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Poke API key — read from 'poke login' credentials, inject into config.yml
|
||||||
|
# The 'poke' npm package writes ~/.config/poke/credentials.json { "token": "..." }
|
||||||
|
# after 'npx poke login'. We auto-read it so the user never has to copy/paste.
|
||||||
|
if grep -q 'your-api-key-here' config.yml 2>/dev/null; then
|
||||||
|
POKE_CREDENTIALS_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/poke/credentials.json"
|
||||||
|
POKE_TOKEN=""
|
||||||
|
|
||||||
|
if [ -f "$POKE_CREDENTIALS_FILE" ]; then
|
||||||
|
POKE_TOKEN=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
data = json.load(open('$POKE_CREDENTIALS_FILE'))
|
||||||
|
print(data.get('token', ''))
|
||||||
|
except Exception:
|
||||||
|
print('')
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$POKE_TOKEN" ]; then
|
||||||
|
echo " ✓ Poke API key detected from 'poke login'"
|
||||||
|
POKE_TOKEN="$POKE_TOKEN" python3 - <<'PYEOF'
|
||||||
|
import os, re, json
|
||||||
|
token = os.environ['POKE_TOKEN']
|
||||||
|
with open('config.yml', 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
pattern = r'(?m)^([ \t]*poke_api_key:[ \t*])[^\n]+'
|
||||||
|
new_content, n = re.subn(pattern, lambda m: m.group(1) + json.dumps(token), content)
|
||||||
|
if n == 0:
|
||||||
|
print(' ⚠ Warning: poke_api_key key not found in config.yml — update it manually.')
|
||||||
|
with open('config.yml', 'w') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
PYEOF
|
||||||
|
echo " ✓ poke_api_key written to config.yml"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo " ⚠ poke_api_key not set in config.yml."
|
||||||
|
echo " Run 'npx poke login' then restart, or paste your key from poke.com/settings/advanced"
|
||||||
|
echo ""
|
||||||
|
printf " Poke API key (leave blank to set manually later): "
|
||||||
|
read -r POKE_TOKEN_INPUT
|
||||||
|
POKE_TOKEN_INPUT=$(echo "$POKE_TOKEN_INPUT" | tr -d '[:space:]')
|
||||||
|
if [ -n "$POKE_TOKEN_INPUT" ]; then
|
||||||
|
POKE_TOKEN="$POKE_TOKEN_INPUT" python3 - <<'PYEOF'
|
||||||
|
import os, re, json
|
||||||
|
token = os.environ['POKE_TOKEN']
|
||||||
|
with open('config.yml', 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
pattern = r'(?m)^([ \t]*poke_api_key:[ \t*])[^\n]+'
|
||||||
|
new_content, n = re.subn(pattern, lambda m: m.group(1) + json.dumps(token), content)
|
||||||
|
if n == 0:
|
||||||
|
print(' ⚠ Warning: poke_api_key key not found in config.yml — update it manually.')
|
||||||
|
with open('config.yml', 'w') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
PYEOF
|
||||||
|
echo " ✓ poke_api_key saved to config.yml"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. MCP_API_KEY — generate once and persist to .env
|
||||||
|
if [ ! -f .env ] \
|
||||||
|
|| grep -Eq '^[[:space:]]*MCP_API_KEY=your-secret-key-here' .env 2>/dev/null \
|
||||||
|
|| ! grep -Eq '^[[:space:]]*MCP_API_KEY=.+' .env 2>/dev/null; then
|
||||||
|
RANDOM_KEY=$(python3 -c "
|
||||||
|
import secrets, string
|
||||||
|
alphabet = string.ascii_letters + string.digits
|
||||||
|
print(''.join(secrets.choice(alphabet) for _ in range(48)))
|
||||||
|
")
|
||||||
|
if [ -f .env ]; then
|
||||||
|
RANDOM_KEY="$RANDOM_KEY" python3 - <<'PYEOF'
|
||||||
|
import os, re
|
||||||
|
new_key = os.environ['RANDOM_KEY']
|
||||||
|
with open('.env', 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
new_content, n = re.subn(r'(?m)^[[:space:]]*MCP_API_KEY=.*', f'MCP_API_KEY={new_key}', content)
|
||||||
|
if n == 0:
|
||||||
|
new_content += f'MCP_API_KEY={new_key}\n'
|
||||||
|
with open('.env', 'w') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
PYEOF
|
||||||
|
else
|
||||||
|
echo "MCP_API_KEY=${RANDOM_KEY}" > .env
|
||||||
|
fi
|
||||||
|
echo " ✓ MCP_API_KEY generated and saved to .env"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Load .env ─────────────────────────────────────────────────────────────────
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
set -a
|
set -a
|
||||||
source .env
|
source .env
|
||||||
set +a
|
set +a
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Activate virtualenv
|
# ── Tunnel-mode detection ─────────────────────────────────────────────────────
|
||||||
source .venv/bin/activate
|
POKE_TUNNEL="${POKE_TUNNEL:-1}"
|
||||||
|
|
||||||
: "${MCP_API_KEY:?MCP_API_KEY is not set — add it to .env or export it}"
|
if [ "${POKE_TUNNEL}" != "1" ]; then
|
||||||
|
: "${MCP_API_KEY:?MCP_API_KEY is not set — add it to .env or export it}"
|
||||||
|
else
|
||||||
|
if [ -z "${MCP_API_KEY:-}" ]; then
|
||||||
|
echo " ℹ MCP_API_KEY not set — server runs unauthenticated (safe: poke tunnel handles auth)."
|
||||||
|
fi
|
||||||
|
export POKE_TUNNEL
|
||||||
|
fi
|
||||||
|
|
||||||
# Start poke-mail server in background
|
# ── Start server + tunnel ─────────────────────────────────────────────────────
|
||||||
echo "Starting poke-mail server..."
|
echo "Starting poke-mail server..."
|
||||||
python src/server.py &
|
python3 src/server.py &
|
||||||
SERVER_PID=$!
|
SERVER_PID=$!
|
||||||
trap "kill $SERVER_PID 2>/dev/null" EXIT
|
trap "kill $SERVER_PID 2>/dev/null" EXIT
|
||||||
|
|
||||||
# Wait for server to be ready
|
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Tunnel to Poke
|
|
||||||
echo "Starting tunnel to Poke..."
|
echo "Starting tunnel to Poke..."
|
||||||
poke tunnel http://localhost:3000/mcp --name "poke-mail"
|
if command -v poke &>/dev/null; then
|
||||||
|
poke tunnel http://localhost:3000/mcp --name "poke-mail"
|
||||||
|
else
|
||||||
|
echo " ℹ 'poke' binary not found in PATH — using npx poke (requires Node.js)."
|
||||||
|
if ! command -v npx &>/dev/null; then
|
||||||
|
echo " ✗ Neither 'poke' nor 'npx' found. Install Node.js (nodejs.org) and run:"
|
||||||
|
echo " npm install -g poke OR npx poke tunnel ..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
npx --yes poke tunnel http://localhost:3000/mcp --name "poke-mail"
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user