Skip to content

bug: ensureBrowser() never auto-logs-in → x_search_tweets / x_crypto_analyze / x_brand_monitor silently return empty #27

@avner-assistant

Description

@avner-assistant

Summary

Two bugs in src/mcp/ cause most scrape tools (x_search_tweets, x_get_hashtag, x_brand_monitor, x_monitor_keyword, x_crypto_analyze, …) to silently return empty results when XACTIONS_SESSION_COOKIE is set the way the README instructs.

Hit these on nirholas/XActions main at commit f30e59781e728234606be600738dc7df2839341e (package version 3.1.0, also current npm latest). Happy to open a PR — the two diffs are below.

Bug 1 — ensureBrowser() never auto-logs-in the singleton browser

Symptom. With XACTIONS_SESSION_COOKIE correctly set via env (or via the MCP server's env block in a Claude Code --mcp-config), calling x_search_tweets / x_get_hashtag / x_brand_monitor / x_crypto_analyze / ~15 other scrape tools returns [] or tweetCount: 0. No error, no log, no rate-limit message.

Repro.

export XACTIONS_SESSION_COOKIE="<valid_auth_token>"
npx -y xactions-mcp
# then call, via any MCP client:
#   x_search_tweets { "query": "bitcoin", "limit": 10 }
# → []

Calling x_get_profile elonmusk in the same session works (it doesn't require login), which makes the bug look like "search is broken" or "cookie expired" rather than an auth-wiring issue.

Root cause. src/mcp/local-tools.js:ensureBrowser() launches a shared Puppeteer browser and reuses it across tool calls, but never calls loginWithCookie:

async function ensureBrowser() {
  if (!browser || !browser.isConnected()) {
    if (browser) { try { await browser.close(); } catch {} }
    browser = await createBrowser();
    page = await createPage(browser);
    // ← nothing consumes SESSION_COOKIE here
  }
  return { browser, page };
}

server.js:49 reads SESSION_COOKIE = process.env.XACTIONS_SESSION_COOKIE and some handlers (x_export_account, algorithm builder, spaces agent, streaming pollers) do call loginWithCookie(page, SESSION_COOKIE) automatically. The shared ensureBrowser() path is the one that doesn't, so search/hashtag/crypto_analyze/brand_monitor/etc. all run against a logged-out singleton page that lands on the x.com login wall; document.querySelectorAll('article[data-testid="tweet"]') returns zero elements; searchTweets() returns [].

Workaround: call the x_login tool manually once at the start of every session. Not documented as required and the tools don't surface any "you are not authenticated" signal, so users currently go down a rabbit hole assuming the cookie is wrong or the scraper is broken.

Proposed fix (src/mcp/local-tools.js):

     browser = await createBrowser();
     page = await createPage(browser);
+    // Auto-login the singleton browser with the env-var cookie so scrape
+    // tools (x_search_tweets, x_get_hashtag, x_brand_monitor,
+    // x_monitor_keyword, x_crypto_analyze, ...) work without requiring a
+    // separate x_login call each session.
+    const envCookie = process.env.XACTIONS_SESSION_COOKIE;
+    if (envCookie) {
+      try {
+        await loginWithCookie(page, envCookie);
+      } catch (e) {
+        console.error('[xactions] auto-login failed:', e.message);
+      }
+    }
   }
   return { browser, page };
 }

loginWithCookie is already imported at the top of local-tools.js, so this is a 12-line addition with no new dependencies.

Bug 2 — x_crypto_analyze reads the wrong shape from x_search_tweets

Symptom. Even after x_login has been called manually to work around Bug 1, x_crypto_analyze always returns tweetCount: 0 with an all-zero sentiment.

Root cause. src/mcp/server.js:2700:

case 'x_crypto_analyze': {
  const searchResults = await localTools.x_search_tweets?.({ query: args.query, limit: args.limit || 100 });
  const tweets = searchResults?.tweets || [];   // ← bug
  ...
}

x_search_tweets returns a plain array of tweet objects, not { tweets: [...] }. searchResults.tweets is always undefined, so tweets is always [], and every subsequent metric is 0.

Proposed fix (src/mcp/server.js):

     case 'x_crypto_analyze': {
       const searchResults = await localTools.x_search_tweets?.({ query: args.query, limit: args.limit || 100 });
-      const tweets = searchResults?.tweets || [];
+      // x_search_tweets returns a plain array, not {tweets:[...]}. Accept
+      // both shapes so tweetCount is populated.
+      const tweets = Array.isArray(searchResults) ? searchResults : (searchResults?.tweets || []);

Verification

Applied both patches locally against a fresh clone of main, ran the MCP with XACTIONS_SESSION_COOKIE set, made zero manual x_login calls:

x_search_tweets { "query": "bitcoin", "limit": 5 }
→ 5 tweets, newest timestamped seconds before the call
  (@AkasoRose25841, @MithebolRaghu, @InvestorsPOV, @Baerental, @ChainBitX)

x_crypto_analyze { "query": "BTC", "timeframe": "24h", "limit": 80 }
→ {
    "query": "BTC",
    "tweetCount": 80,
    "sentiment": { "bullish": 28, "bearish": 11, "neutral": 61, "score": 16 },
    "topInfluencers": [ { "username": "nehalzzzz1", "engagement": "060" } ]
  }

Both numbers were 0/[] before the patches.

Minor observation (not part of the proposed fix)

The x_crypto_analyze influencer filter requires engagement > 50 on (likes + retweets), but x_search_tweets returns "likes": "0" for most live-feed tweets because X reports zero engagement for tweets only seconds old. As a result, topInfluencers is usually a one-item array or empty on short timeframes. Might be worth either lowering the threshold, pulling from f=top for the influencer pass, or weighting by impressions instead. Happy to include in a follow-up PR if there's interest — keeping this issue focused on the two correctness bugs.

Environment

  • xactions 3.1.0 (latest on npm and on main)
  • Node.js 22.22.0 on Linux x86_64
  • Puppeteer adapter (default)
  • Invoked as an MCP stdio server with XACTIONS_SESSION_COOKIE set in the process env at spawn time

Thanks for the project — the tool surface is genuinely impressive, and once past these two bugs the crypto-sentiment stack is in great shape for what we're building on top.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions