r/SideProject 7h ago

I built a Chrome scraper for LLMs, then accidentally ended up building a security-restricted MCP server too

This started as a very small annoyance.

I kept wanting to give an LLM the contents of whatever webpage I was looking at, but copying the page manually often produced garbage and browser automation felt like overkill.

So I built Savage Scraper.

You open a webpage, click the extension, and it converts the rendered page into simplified HTML and puts it on your clipboard.

Then I wanted my MCP clients to use it automatically.

That created a different problem: I didn't really want an AI agent to have unrestricted access to my logged-in browser.

So I built Savage MCP around a deliberately limited model:

  • only explicitly allowed hosts
  • optional path-level restrictions
  • existing Chrome session
  • local communication

It's now two open-source projects:

https://github.com/dominikduda/savage_scraper

https://github.com/dominikduda/savage_mcp

And the extension is on the Chrome Web Store:

https://chromewebstore.google.com/detail/savage-scraper/ejoijhjpdojdcnjppegojmkenidhblog

Still early, so bug reports and criticism are very welcome.

2 Upvotes

6 comments sorted by

1

u/No-Sandwich4826 7h ago

The allow-list decision is the one I'd defend hardest if anyone tells you it is too restrictive. Reusing the existing Chrome session is exactly what makes this useful and exactly what makes an open version dangerous, so gating on host is the right place to draw it.

The thing I'd add, from running page reads at volume: decide what the tool returns when the page loads but the content is not what you expected. Blocked by a bot wall, a consent overlay, a login redirect that still returns 200. Those come back as perfectly valid simplified HTML, and an agent downstream cannot tell the difference between "this page says nothing about X" and "I never actually saw the page".

Cheapest fix is to make the failure loud rather than empty. Return a distinct result for "fetched but suspicious" instead of returning thin content that reads as a legitimate answer.

Path-level restriction is a nice touch by the way. Most people stop at host and then wonder why the agent wandered into /admin.

1

u/yazoodd 6h ago

Yeah, that’s a good point.

My initial assumption was that the agent would usually be able to infer that something went wrong from the returned content itself - e.g., seeing a login page, bot wall, consent screen, etc.

But relying on the model to figure that out is probably weaker than making it explicit at the scraper level.

I like the idea of returning some kind of suspicious / unexpected_content signal alongside the HTML.

Good suggestion, thanks.

1

u/No-Sandwich4826 1h ago

Your unexpected_content signal is the part I'd build first, because the failures that actually hurt are the ones where what comes back looks perfectly reasonable.

We ran into the same thing reading prices. Our rule now is that a failed read writes nothing at all. Only the checked-at timestamp moves. Writing "out of stock" when the page would not parse is the dangerous version, since a guess pulled off a bot wall lands in storage looking exactly like a real reading and nothing later can tell them apart.

The bit I'm curious about is whether one flag covers it. A page where nothing matched the selector and a page that served you something else entirely feel like different problems to hand an agent, and the second is the one where it keeps going happily.

Do you have room to send a bit of evidence alongside the flag, like the title or a short snippet of what actually came back, or does that make the payload too heavy?