diff --git a/README.md b/README.md index afda11c..9752e79 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,38 @@ substack-mcp-setup If Substack sent a sign-in email, paste the email link into the same setup browser window before closing it. +### Magic-link email never arrives + +Substack's anti-bot layer sometimes silently drops the magic-link email when +it is requested from the automated setup browser — no error is shown and the +wizard waits forever. The fallback is manual session-cookie auth, which the +server already supports through the `SUBSTACK_SESSION_TOKEN` environment +variable: + +1. In your normal browser, already signed in to Substack, open + `https://substack.com`. +2. Open DevTools → Application tab (Chrome) or Storage (Firefox/Safari) → + Cookies → `https://substack.com`. +3. Copy the value of the `substack.sid` cookie. Either the decoded `s:...` + form or the URL-encoded `s%3A...` form works — the token is passed + directly as the cookie value. +4. Set it as `SUBSTACK_SESSION_TOKEN` in your MCP client config alongside + `SUBSTACK_PUBLICATION_URL`. For Claude Code: + + ```bash + claude mcp add substack --scope user \ + --env SUBSTACK_PUBLICATION_URL=https://YOUR_PUBLICATION.substack.com \ + --env SUBSTACK_SESSION_TOKEN=PASTED_COOKIE_VALUE \ + -- substack-mcp + ``` + + For Claude Desktop and other clients, add both variables to the server's + `env` block instead. + +This cookie is password-equivalent — treat it like a secret. To revoke it, +sign out of all sessions in your Substack account settings, then capture a +fresh cookie. + ### MCP shows connected but tools do not appear This is usually a client session cache issue. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 822418c..97e73e2 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -119,6 +119,7 @@ Upload image from https://picsum.photos/800/400 optimized for web | "No authentication found" | Run `substack-mcp-setup` | | CAPTCHA appears | Solve it in the browser window | | Email link opens elsewhere | Copy the link and paste it into the setup browser | +| Magic-link email never arrives | Substack sometimes silently drops it for automated browsers — use the [session-token fallback](#session-token-if-you-have-one) | | Session expired | Run `substack-mcp-setup` again | | Import errors | Reinstall with `npm install -g github:IgnazioDS/Substak-MCP` | @@ -141,11 +142,24 @@ export SUBSTACK_PUBLICATION_URL="https://YOUR_PUBLICATION.substack.com" ``` ### Session Token (If you have one) + +This is also the reliable fallback when the wizard's magic-link email never +arrives (Substack sometimes silently drops it for automated browsers). To get +the token: in your normal signed-in browser open `https://substack.com`, then +DevTools → Application (Chrome) / Storage (Firefox/Safari) → Cookies → +`https://substack.com`, and copy the `substack.sid` cookie value (the `s:...` +and `s%3A...` forms both work). + ```bash export SUBSTACK_SESSION_TOKEN="YOUR_SUBSTACK_SESSION_TOKEN" export SUBSTACK_PUBLICATION_URL="https://YOUR_PUBLICATION.substack.com" ``` +This cookie is password-equivalent — treat it like a secret. Revoke it by +signing out of all sessions in your Substack settings. + +Full walkthrough: [Authentication Guide](authentication.md#method-2-session-token-advanced). + ## 📚 More Resources - [Full Documentation](docs/) diff --git a/docs/authentication.md b/docs/authentication.md index 75741c7..0a4a66a 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -119,13 +119,27 @@ export SUBSTACK_PUBLICATION_URL="https://YOUR_PUBLICATION.substack.com" ### Method 2: Session Token (Advanced) -If you already have a session token: +Use this when the setup wizard cannot complete — most commonly when +Substack's anti-bot layer silently drops the magic-link email requested from +the automated browser (no error is shown; the wizard just waits). + +Grab the session cookie from a browser where you are already signed in: + +1. Open `https://substack.com` in your normal browser +2. Open DevTools → **Application** tab (Chrome) or **Storage** (Firefox/Safari) → Cookies → `https://substack.com` +3. Copy the value of the `substack.sid` cookie — the decoded `s:...` form and the URL-encoded `s%3A...` form both work, since the token is passed directly as the cookie value + +Then set it: ```env -SUBSTACK_SESSION_TOKEN=YOUR_SUBSTACK_SESSION_TOKEN +SUBSTACK_SESSION_TOKEN=PASTED_COOKIE_VALUE SUBSTACK_PUBLICATION_URL=https://YOUR_PUBLICATION.substack.com ``` +**Security note**: this cookie is password-equivalent — treat it like a +secret. To revoke it, sign out of all sessions in your Substack account +settings, then capture a fresh cookie. + ## 🚨 Troubleshooting ### "No authentication found" Error @@ -144,6 +158,14 @@ The setup wizard handles CAPTCHA automatically. If you still have issues: `substack-mcp-setup`. The setup must see the final signed-in browser state before it can store the session. +### Magic-Link Email Never Arrives +Substack's anti-bot layer sometimes silently drops magic-link emails +requested from the automated setup browser — no error appears and the wizard +waits forever. + +**Solution**: Use the manual session-cookie fallback described in +[Method 2: Session Token](#method-2-session-token-advanced). + ### Session Expired **Solution**: Run `substack-mcp-setup` to refresh diff --git a/pyproject.toml b/pyproject.toml index 6957886..5cf8360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ # python-substack wraps reverse-engineered/private behaviors, so we keep an # upper bound until each newer release is contract-tested against our fixtures. "python-substack>=0.1.20,<0.1.23", - "mcp>=1.10.0", + "mcp>=1.10.0,<2", # mcp 2.0 removed the low-level Server.list_tools() decorator API "markdown2>=2.4.10", "pydantic>=2.5.0", "python-dotenv>=0.21.0,<1.0.0", diff --git a/setup_auth.py b/setup_auth.py index e4bacf7..425d085 100755 --- a/setup_auth.py +++ b/setup_auth.py @@ -173,7 +173,7 @@ async def _authenticate_with_browser(self) -> Optional[Dict[str, str]]: # Navigate to Substack login logger.info("Navigating to Substack login...") await page.goto( - "https://substack.com/sign-in", wait_until="networkidle" + "https://substack.com/sign-in", wait_until="domcontentloaded" ) # Use a manual-first flow because Substack's auth UI changes frequently,