Skip to main content
Glama
Perufitlife

multi-scraper-mcp

by Perufitlife

MCP server as an Apify Actor — a working reference

This is the source behind renzomacar/multi-scraper-mcp, an MCP server that runs as an Apify Actor in Standby mode and exposes 16 lead-generation and web-scraping tools to AI clients like Claude, Cursor and Windsurf.

It is published as a reference because the interesting part is not the tool list — it is the three transport bugs that kept real clients from connecting while every run reported SUCCEEDED.

The three failures this code fixes

1. A stale session id used to wedge the client forever

Sessions live in an in-memory Map, but a Standby run is not one long-lived process — Apify recycles it. A client that reconnects on Tuesday with Monday's Mcp-Session-Id hits a process that has never heard of it.

The server used to answer 400. The spec is explicit that the answer must be 404:

The server MAY terminate the session at any time, after which it MUST respond to requests containing that session ID with HTTP 404 Not Found.

When a client receives HTTP 404 [...] it MUST start a new session by sending a new InitializeRequest without a session ID attached.

The client's recovery path is keyed to the status code. 400 removes its ability to heal itself. See sessionGone() in src/main.js.

2. The Accept header rejected clients before any of our code ran

The spec requires clients to send Accept: application/json, text/event-stream. Several real clients send only one, or nothing, and the transport answers 406 before the request reaches a handler — so there is nothing in the logs and no failed run in the console.

normalizeAccept() accepts them anyway. The catch: the transport reads the header from req.rawHeaders, so patching req.headers alone does nothing.

3. A slow tool dropped the connection mid-work

These tools wrap scrapers that take one to three minutes. Without bytes on the wire the Standby gateway drops the connection. The fix streams a notifications/message every 10 seconds, and clears the interval in finally so an early error return cannot leak the timer.

Related MCP server: Kryfto

Billing honestly

Actor.charge sits on the path that actually returned data, after the items are in hand — not at the top of the request handler. Charging on arrival means charging for failures.

Layout

file

what's in it

src/main.js

Actor init, Express app, Streamable HTTP + legacy SSE transports, the three fixes

src/tools.js

tool registry: name, description, input schema, target Actor

.actor/

Actor definition, input and output schemas

Running it

npm install
npm start

Then point an MCP client at http://localhost:4321/mcp. On the Apify platform the Standby URL is https://USERNAME--ACTOR-NAME.apify.actor/mcp — note the USERNAME-- prefix; publishing the URL without it makes every copy-paste fail.

A check worth running

Six requests, each one a bug that shipped:

  1. handshake with a correct Accept header → 200

  2. handshake with Accept: application/json → 200 (was 406)

  3. handshake with no Accept header → 200 (was 406)

  4. tools/list with an unknown session id → 404 (was 400)

  5. initialize while still holding a stale id → 200 (was 400)

  6. tools/list on a live session → 200, full tool list

Numbers 2 through 5 were all broken in production while the Actor reported healthy runs.

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    An APAC-native web scraping API for AI agents that provides tools for scraping, crawling, searching, and extracting structured data from websites, directly usable from MCP-compatible clients like Claude Desktop, Cursor, and Windsurf.
    7
    7
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides 42+ MCP tools for browser automation, web scraping, and search, enabling AI agents like Claude and Cursor to browse, extract data, and run research agents on the live web.
    8
    -