DEV Community

Cover image for My browser MCP server stopped fighting LinkedIn the second I stopped making it do login
Lars Winstand
Lars Winstand

Posted on • Originally published at standardcompute.com

My browser MCP server stopped fighting LinkedIn the second I stopped making it do login

I hit the same wall 12 times in a row.

Playwright opened LinkedIn, bounced through the login screen, hesitated on the email field, tripped over a redirect, and then my agent started inventing reasons it "couldn't proceed safely."

GitHub wasn't much better.

I tried the usual fixes first:

  • better prompts
  • stricter step-by-step instructions
  • smaller tasks
  • more retries
  • more guardrails

None of it mattered.

The thing that finally fixed it was embarrassingly simple:

I stopped giving the browser agent the login job.

While digging into this, I found a thread on r/openclaw about Conneclaw refusing to scrape LinkedIn, and it matched what I was seeing almost exactly. Another thread about GitHub login had the same energy: the model looked flaky, but the real problem was architectural. Login had been shoved into the browser loop.

That is the mistake.

If your browser MCP server or browser agent keeps failing on LinkedIn or GitHub login, stop prompt-tuning it after the 5th retry. Move authentication outside the browser loop with Playwright session state, OAuth, API tokens, or a dedicated MCP server. Let the browser do navigation only.

After I made that change, repeated failures went from basically every run to almost none.

The actual problem: login is not a browsing task

People talk about browser agents like they should handle everything visible in a tab.

That sounds nice until the tab is actually an identity boundary.

LinkedIn login is not just:

  1. open page
  2. type email
  3. type password
  4. click submit

It's more like:

  • redirects
  • session checks
  • anti-bot checks
  • MFA
  • consent flows
  • suspicious login prompts
  • weird intermediate states that only appear sometimes

GitHub has the same problem, just with different screens.

Once you put that inside an agent loop, you're asking the model to manage account authority, security state, and navigation all at once.

That's where things get cursed.

The failure pattern is usually the same:

  1. The agent navigates correctly.
  2. It hits a login wall.
  3. You assume the model is weak.
  4. You spend more model calls teaching it how to click through identity flows.
  5. The workflow gets slower, more expensive, and less reliable.

If you're running automations in n8n, Make, Zapier, OpenClaw, or your own scheduler, one bad login loop can poison an entire run.

A task that should be:

  • open page
  • extract data
  • continue

turns into 20 extra model calls and a dead workflow.

My opinionated rule

Browser agents should not own primary login for LinkedIn or GitHub.

Not in Playwright.
Not in a browser MCP server.
Not in OpenClaw.
Not in custom agent frameworks.

If your browser agent is typing passwords into LinkedIn or GitHub on every run, the architecture is wrong.

What should own auth instead?

Use a dedicated authenticated path.

That usually means one of these:

  • API token + direct API access
  • OAuth handled outside the agent
  • Playwright storageState captured once and reused
  • a service-specific MCP server like a GitHub MCP server

The split should look like this:

Job Owner
Account authority OAuth, API token, session bootstrap, dedicated MCP server
Browser navigation Playwright or browser MCP server
Data extraction from rendered page Browser automation
GitHub actions like issues, PRs, comments GitHub API or GitHub MCP server

That separation matters more than people think.

The Model Context Protocol is already pointing in this direction: tools should expose clear capabilities with clear authority boundaries.

Browser navigation is one capability.

Account authority is another.

Mixing them into one loop is what creates chaos.

What I changed

1) For GitHub, I stopped using the browser for account actions

If the task is "create an issue" or "read repo metadata," use the GitHub API or a GitHub MCP server.

Not a browser tab.

Example with the GitHub CLI:

gh auth login
gh issue create \
  --repo owner/repo \
  --title "Bug: login loop in browser agent" \
  --body "Observed repeated login failures when auth was handled inside Playwright."
Enter fullscreen mode Exit fullscreen mode

Or plain HTTP:

curl -X POST https://api.github.com/repos/OWNER/REPO/issues \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  -d '{
    "title": "Bug: login loop in browser agent",
    "body": "Auth belongs outside the browser loop."
  }'
Enter fullscreen mode Exit fullscreen mode

That removes a whole class of failure immediately.

2) For browser-only tasks, I authenticated once and reused session state

If I actually needed a rendered page, I used Playwright session state instead of making the agent log in every run.

import { chromium } from 'playwright';

async function bootstrapSession() {
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext();
  const page = await context.newPage();

  await page.goto('https://www.linkedin.com/login');

  console.log('Log in manually, then press Ctrl+C after storageState is saved.');

  await page.waitForURL('**/feed/**', { timeout: 0 });
  await context.storageState({ path: 'linkedin-session.json' });

  await browser.close();
}

bootstrapSession();
Enter fullscreen mode Exit fullscreen mode

Then reuse it in the automation:

import { chromium } from 'playwright';

async function runScrape() {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    storageState: 'linkedin-session.json'
  });

  const page = await context.newPage();
  await page.goto('https://www.linkedin.com/company/some-company/');

  const title = await page.locator('h1').textContent();
  console.log({ title });

  await browser.close();
}

runScrape();
Enter fullscreen mode Exit fullscreen mode

Now the browser is doing what it is good at:

  • opening pages
  • waiting for rendered content
  • extracting data
  • clicking normal navigation elements

Not doing what it is bad at:

  • being your identity layer

3) For internal tools, I passed a constrained session into the workflow

Same idea.

Authenticate outside the agent. Then pass only the scoped credentials or session the job actually needs.

That can be OAuth tokens, cookies in a secure secret store, short-lived credentials, or a service wrapper that exposes a narrow action surface.

A simple architecture that works better

Here's the pattern I wish I had started with:

[Scheduler / n8n / Make / Zapier / custom worker]
                |
                v
      [Auth bootstrap / token provider]
                |
        -------------------
        |                 |
        v                 v
[GitHub MCP/API]   [Playwright browser session]
        |                 |
        v                 v
  account actions     rendered-page tasks
Enter fullscreen mode Exit fullscreen mode

The browser is one component, not the whole system.

That sounds obvious after the fact. It did not feel obvious while I was burning retries on a login page.

Why this matters more for agent workflows

If you're running one-off scripts, a flaky login is annoying.

If you're running agents 24/7, flaky login is a tax on everything:

  • more retries
  • more latency
  • more brittle runs
  • more model calls wasted on dead-end screens
  • more debugging time on something that was never the model's job

And yes, more cost.

This is where the pricing model starts to matter.

When workflows keep bouncing off auth walls, you end up spending model calls on nonsense instead of useful work. That's exactly the kind of waste that makes per-token billing feel terrible for automations.

If you run lots of agent loops in n8n, Make, Zapier, OpenClaw, or custom workers, this is why predictable flat-rate compute is so appealing. You still want to fix the architecture, obviously. But when you're iterating on real automations, not having every failed retry show up as another tiny billing event is a huge relief.

That's one of the reasons Standard Compute is interesting for this kind of workload: it's a drop-in OpenAI-compatible API with flat monthly pricing, so you can run agent-heavy workflows without constantly watching token burn. If your automations are doing real work all day, that pricing model makes a lot more sense than paying for every detour and retry.

A good rule of thumb

Use the browser only when you need browser-specific capabilities.

Use APIs or service-specific MCP tools when the task is really a system action.

Examples:

Task Best tool
Create GitHub issue GitHub API or GitHub MCP server
Read repo PR metadata GitHub API or GitHub MCP server
Navigate a rendered LinkedIn page after auth exists Playwright
Click through a normal app UI flow Playwright or browser MCP server
Handle OAuth, token exchange, session bootstrap Backend service or auth layer

If a browser tab is pretending to be your secure identity system, you're going to have a bad time.

The result after the split

The browser stopped acting haunted.

Once I removed login from the browser agent's job:

  • Playwright opened the page
  • navigated normally
  • extracted what I needed
  • moved on

GitHub actions went through the GitHub API path instead of pretending a browser tab was a trustworthy authority layer.

The agent did less.

The system did more.

That's the part people miss.

This is not about elegance. It's about reliability.

A lot of teams see these failures and decide they need a smarter model.

I think that's usually the wrong diagnosis.

Most of the time, the fix is to stop using the browser as your auth system.

Practical checklist

If your browser MCP server keeps failing on LinkedIn or GitHub, try this:

[ ] Stop retrying the same login prompt
[ ] Remove password entry from the agent loop
[ ] Use API tokens where possible
[ ] Use a GitHub MCP server or direct GitHub API for GitHub actions
[ ] Capture Playwright storageState for browser-only tasks
[ ] Store sessions securely and rotate them when needed
[ ] Keep browser automation focused on navigation and extraction
[ ] Measure failures before and after the split
Enter fullscreen mode Exit fullscreen mode

Final take

My browser MCP server stopped fighting LinkedIn the second I stopped making it do login.

Not because the model got smarter.

Because I finally stopped asking Playwright, LinkedIn, GitHub, and MCP to do the wrong job in the same place.

If your agent keeps getting stuck at the login wall, don't spend another afternoon prompt-tuning the password field.

Split auth from navigation.

That's the fix.

Top comments (0)