Most MCP demos show a tool that answers in a second. Real tools often don't: a scraper, a crawl or a report job can take minutes. We build web scrapers on Apify, so this is a problem we run into daily. We wanted to know what an agent actually receives when a remote MCP tool runs longer than one call, so we traced the raw JSON-RPC traffic against Apify's hosted MCP server (mcp.apify.com, version 0.17.1 at the time) on Oct 2, 2026. The job: a YellowPages scraper reading one results page, which takes about 35 seconds.
After initialize, tools/list returned one tool per scraper we had allowed through the server URL, plus four helper tools that only make sense for long-running work:
get-actor-run checks a run's status and can wait for it,get-dataset-items reads the results in pages,get-key-value-store-record reads a stored file or record,abort-actor-run stops a run.
The helpers are the first hint: the server expects the agent to come back for results.
The agent calls the scraper tool with ordinary arguments:
{ "searchKeyword": ["plumbers"], "location": "Austin, TX", "maxPages": 1 }
The response arrived after about 30 seconds, while the job was still running. It had two content parts. The first is structured:
{
"runId": "…",
"status": "RUNNING",
"storages": { "datasets": { "default": { "id": "…", "itemCount": 23 } } }
}
The second is plain text written for the model:
RUNNING for 30s. In progress. 23 results so far.
Use get-actor-run with runId=… and waitSecs=30 to poll for completion.
That second part is the interesting design choice. The server returns machine-readable state and also tells the model, in plain English, what to do next. An agent that only parses the JSON still works. An agent that only reads text also knows to poll.
We first asked get-actor-run to wait 60 seconds and then 120. Both came back as a JSON-RPC error, not a tool result:
MCP error -32602: Invalid arguments for tool "get-actor-run".
Validation errors: /waitSecs: must be <= 45.
With waitSecs: 30 the call returned SUCCEEDED, the run time (about 35 seconds) and the dataset item count (37). The result also carried a _meta block with the run's platform usage, which a client can show the user before it starts anything bigger.
Two things to handle here:
-32602), not as tool results with isError. If your client only checks get-dataset-items takes the dataset ID plus offset and limit, and returns:
{ "datasetId": "…", "items": [ … ], "itemCount": 37, "totalItemCount": 37, "offset": 0, "limit": 100 }
For a big job, page through with offset instead of pulling everything into the model's context at once.
The same evening a second test job, a Google Maps scraper, ran for about 157 seconds and finished with status SUCCEEDED, but get-dataset-items came back empty:
{ "items": [], "itemCount": 0, "totalItemCount": 0, "offset": 0, "limit": 100 }
An upstream dependency was failing, and the job ended cleanly with nothing to show. If your agent treats SUCCEEDED as "done, report success", it will tell the user everything worked. Check the item count, and treat "succeeded but empty" as a result the user needs to hear about.
How does your MCP client deal with tools that outlive one call: polling, progress notifications, or something else? We're curious what holds up in production.
Written with AI assistance. Every request and response above comes from real calls we made on Oct 2, 2026.