From 581dd0b89af5f414f8732e6dbfb02b928c8668b9 Mon Sep 17 00:00:00 2001 From: 0xK Date: Tue, 24 Mar 2026 11:40:26 +0100 Subject: [PATCH] fix: optional MCP_API_KEY in tunnel mode + address Copilot review issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - start.sh: detect POKE_TUNNEL env var; skip MCP_API_KEY requirement and auth when running via poke tunnel (server.py reads the same var) - start.sh: pass POKE_TOKEN into Python via env var + use json.dumps to safely escape quotes/backslashes in YAML (fixes shell-interpolation injection risk, Copilot issue #5 / start.sh:72) - start.sh: anchor MCP_API_KEY guard to non-commented line-start assignments and also detect empty value (Copilot issues #1, #8 / start.sh:104) - start.sh: anchor re.sub pattern with re.MULTILINE so only the actual assignment line is rewritten, not mid-line occurrences (Copilot issue #2) - start.sh: check re.sub replacement count, warn when poke_api_key key is missing from config.yml (Copilot issue #6) - start.sh: guard npm/npx usage with command -v check; fall back to npx poke instead of hard-failing (Copilot issue #3) - start.sh: use python3 consistently for server.py (Copilot issue #9 / start.sh:134) - start.sh: prefer npx poke tunnel; check command -v poke and fall back gracefully (Copilot issue #10 / start.sh:135) - server.py: honour POKE_TUNNEL=1 — skip bearer-token auth so the poke tunnel handles identity; MCP_API_KEY becomes optional in that mode - README.md: add Node.js/npm prerequisite note (Copilot issue #4) - README.md: clarify server starts on first run; update AI agent prompt (Copilot issue #11 / README.md:48) --- README.md | 18 +++++++++---- src/server.py | 20 +++++++++++--- start.sh | 74 +++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 87 insertions(+), 25 deletions(-) mode change 100755 => 100644 src/server.py diff --git a/README.md b/README.md index 91491e8..df24a41 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ An MCP server that bridges IMAP/SMTP email accounts to [Poke](https://poke.com). ## Quick Start +**Prerequisites:** Python 3.10+ and Node.js 18+ (which includes `npx` and `npm`). + ```bash git clone https://github.com/kacperkwapisz/poke-mail.git cd poke-mail @@ -35,8 +37,11 @@ On the **first run**, `start.sh` automatically handles the full setup: 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 that first run, open `config.yml` and fill in your email account credentials. Run `./start.sh` again to start the server. +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. @@ -45,7 +50,7 @@ On **subsequent runs**, `start.sh` skips setup and goes straight to starting the 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, and set up the virtualenv — 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) — then run ./start.sh again to start the server and tunnel it to Poke. +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 @@ -98,7 +103,7 @@ pip install -r requirements.txt ### 3. Run ```bash -MCP_API_KEY=your-secret-key python src/server.py +MCP_API_KEY=your-secret-key python3 src/server.py ``` ### 4. Test @@ -113,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 `. -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. @@ -169,7 +176,8 @@ The server is mostly idle (IMAP IDLE + lightweight HTTP). Recommended limits for | 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 | | `POKE_WEBHOOK_URL` | from config | Overrides webhook URL in config | | `POKE_API_KEY` | from config | Overrides Poke API key in config | diff --git a/src/server.py b/src/server.py old mode 100755 new mode 100644 index 3525d65..651380b --- a/src/server.py +++ b/src/server.py @@ -492,10 +492,24 @@ async def lifespan(server: FastMCP): # --------------------------------------------------------------------------- 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( - "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) diff --git a/start.sh b/start.sh index 7032b37..a18634f 100644 --- a/start.sh +++ b/start.sh @@ -52,7 +52,7 @@ if grep -q 'your-api-key-here' config.yml 2>/dev/null; then if [ -f "$POKE_CREDENTIALS_FILE" ]; then POKE_TOKEN=$(python3 -c " -import json +import json, sys try: data = json.load(open('$POKE_CREDENTIALS_FILE')) print(data.get('token', '')) @@ -63,11 +63,17 @@ except Exception: if [ -n "$POKE_TOKEN" ]; then echo " ✓ Poke API key detected from 'poke login'" - python3 - <"${POKE_TOKEN}"', content) +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 @@ -81,11 +87,15 @@ PYEOF read -r POKE_TOKEN_INPUT POKE_TOKEN_INPUT=$(echo "$POKE_TOKEN_INPUT" | tr -d '[:space:]') if [ -n "$POKE_TOKEN_INPUT" ]; then - python3 - <"${POKE_TOKEN_INPUT}"', content) +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 @@ -96,20 +106,27 @@ PYEOF fi # 4. MCP_API_KEY — generate once and persist to .env -if [ ! -f .env ] || grep -q 'your-secret-key-here' .env 2>/dev/null || ! grep -q 'MCP_API_KEY=' .env 2>/dev/null; then +# Regenerate when: .env is missing, contains the placeholder, or has an +# empty assignment (MCP_API_KEY=) which would still fail at the :? check. +# Anchored to non-commented, line-start assignments only. +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 - python3 - </dev/null" EXIT # Wait for server to be ready sleep 2 -# Tunnel to Poke +# Tunnel to Poke — prefer the globally-installed poke binary; fall back to npx. 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