how to deploy an mcp server on apify that survives the daily test
an MCP server on apify is a standby actor that speaks streamable HTTP at /mcp, answers a readiness probe on GET /, and also runs in batch mode producing a dataset item, because the platform automatically tests public actors roughly daily and deprecates the ones that fail. that sentence took me three broken build cycles to earn. here's the architecture so you can skip them.
context: i've shipped 19 MCP servers to the apify store this way (the business side is in how to sell mcp servers on apify). the template below is the one all 19 run on, and it has passed every one of the platform's checks since it stabilized.
the wrong mental model (start here)
if you've built MCP servers for claude desktop, you know them as stdio processes: a binary the client spawns and pipes JSON through. my first apify push assumed exactly that. "MCP is a stdio binary, apify is a docker host, ship it." wrong on every axis. apify actors are containers the PLATFORM runs, reached over HTTP, tested automatically, and killed politely with SIGTERM. the transport, the lifecycle, and the health checks are all different.
the five pieces every surviving server has
- actor.json that declares standby.
usesStandbyMode: true,webServerMcpPath: "/mcp", plus an input schema, dataset schema, and an openAPI file for the web server. the input schema must carry"schemaVersion": 1and every property needs a description and an editor, or the build fails. - streamable HTTP, not stdio, not SSE. apify removed SSE in april 2026. the server handles
POST /mcpwith the MCP SDK'sStreamableHTTPServerTransport. clients must send an accept header covering bothapplication/jsonandtext/event-stream. - the readiness probe. standby never marks your actor ready without it:
app.get("/", (req, res) => {
if (req.headers["x-apify-container-server-readiness-probe"]) {
res.status(200).type("text/plain").send("ready");
return;
}
res.status(200).json({ server: "your-mcp", endpoint: "/mcp" });
});
app.listen(port, "0.0.0.0");
two details in there cost me real hours: the probe header check, and binding explicitly to 0.0.0.0. without the explicit host, node can bind localhost-only and the probe times out forever while your logs look perfectly healthy.
- the dual-mode batch branch. when
APIFY_META_ORIGINisn'tSTANDBY, read the input, run one tool, push the result to the dataset, exit. this branch is what apify's automated daily test exercises with your schema's prefill values. if it produces an empty dataset, a deprecation countdown starts. silently. - a clean shutdown. handle SIGTERM, close the HTTP server, exit. standby pods get recycled; actors that ignore the signal get killed mid-request.
the dockerfile that works
FROM apify/actor-node:24 COPY --chown=myuser:myuser package*.json ./ RUN npm --quiet set progress=false \ && npm install --include=dev --no-optional --no-audit --no-fund COPY --chown=myuser:myuser tsconfig.json ./ COPY --chown=myuser:myuser src ./src RUN ./node_modules/.bin/tsc RUN npm prune --omit=dev --no-audit --no-fund EXPOSE 4321 CMD ["node", "dist/main.js"]
one trap hiding in plain sight: npm install --no-save typescript && npx tsc resolves a wrong, ancient tsc package. install dev deps, call ./node_modules/.bin/tsc directly, prune after.
verify before you publish: the three checks
after every apify push, i run the same three checks, and nothing goes public until all three pass:
- probe:
GET https://<user>--<actor>.apify.actor/with the readiness header returns 200. - tools/list: a
POST /mcpwith a tools/list request returns a non-empty tool array. - batch run:
run-sync-get-dataset-itemswith the default input returns at least one item with a real result.
this is the same discipline as the verification layer applied to deployment: the deploy saying "success" is not evidence the thing works. only the checks are.
traps that don't announce themselves
- the 401 head-fake. hitting a standby URL without a token returns 401 even on public actors. that 401 is auth, not privacy; don't diagnose visibility from it.
- version pinning. the version in actor.json must match what the platform already has, or the push errors with "actor version was not found."
- store limits. max 3 categories per actor, and max 5 actor publications per day account-wide. plan batch launches across days.
- cloud-synced source folders. if your source lives in a cloud-synced folder, files can be evicted mid-push and ship as empty. i once shipped an empty dockerfile this way. keep actor source on plain local disk.
frequently asked questions
why does my actor never become ready in standby mode?
the readiness probe, almost every time. return 200 to the probe header on GET / and bind to 0.0.0.0. both, not either.
does apify still support sse for mcp?
no, removed april 2026. use streamable HTTP.
what exactly does the daily test run?
your actor in batch mode with the input schema's prefill values. that's why the dual-mode branch and sensible prefills are not optional: they ARE the test.
can i write this in python?
apify supports python actors, but the streamable HTTP + standby pattern here uses the typescript MCP SDK, and it's the combination i've verified across 19 servers. the concepts (probe, dual mode, SIGTERM) transfer directly.
i didn't hand-type most of this code: claude code built it against this exact spec, and my job was the spec plus the three checks. the workflow, skills, and verification gates that make that safe are in the AI builder toolkit at closermethod.com/skills.
go deeper
- how to sell mcp servers on apify (19 live, the honest version)
- how to price an mcp server (pay-per-event, explained with real numbers)
- apify vs self-hosting your mcp server: the real trade
- the verification layer: why every ai workflow needs one
sources: apify documentation for actor standby mode, MCP integration, actor definition, and publishing tests; anthropic model context protocol SDK documentation; every behavior described (probe requirement, SSE removal, daily test, publication limits, schema validation errors) hit and verified first-hand on the live platform between may and july 2026, most recently july 27, 2026. written by elisabeth hitz, certified in anthropic's ai fluency program (framework & foundations, and ai capabilities & limitations), plus claude 101 and claude cowork.