A default MCP connection hands an agent 11 tools. None of my 23 Actors is one of them.
I have 23 audit Actors on the Apify Store. They all do a version of the same thing: take a public record, check whether what it still claims is true, and write the verdict into a dataset.
I have also written six articles about what happens when an AI agent calls one of them through the Apify MCP server. Output fields that arrive empty. Input schemas that read differently to a form and to a tool. A default that quietly fills a required field. A connector that shows 44 tools and grants four.
MCP is the Model Context Protocol, the interface that turns an Actor into a tool that clients like Claude and Cursor can call on their own. Every one of those six articles assumed the agent had that tool in hand. Last week I checked the assumption for the first time. It does not hold by default.
The connection string I never read
I found the problem in my own machine before I found it anywhere else. This is the Apify entry in my Claude config, verbatim except that there is no token in it:
"url": "https://mcp.apify.com?tools=aiqlabs/github-repository-audit"
I wrote that line months ago to test one Actor in isolation, and then left it. My editor session had five Apify tools in it. One of them was an Actor, and it was the one named in that string.
So I had spent six articles reasoning about how agents see my catalogue. The session I was reasoning from had been narrowed to a single Actor, by me, on purpose, and then forgotten. The first thing worth measuring was what a connection with nothing in it returns.
What a default connection actually sends
Twenty lines, no dependencies. This is the whole test:
// reach.mjs — what does a default connection to the Apify MCP server actually send?
// Run: APIFY_TOKEN=... node reach.mjs
const URL = `https://mcp.apify.com/?token=${process.env.APIFY_TOKEN}`;
const HEAD = { 'content-type': 'application/json', accept: 'application/json, text/event-stream' };
async function rpc(body, sid) {
const res = await fetch(URL, { method: 'POST', body: JSON.stringify(body),
headers: sid ? { ...HEAD, 'mcp-session-id': sid } : HEAD });
const text = await res.text();
// the server answers as SSE, so the JSON sits on a "data:" line
const line = text.split('\n').find((l) => l.startsWith('data:'));
return { sid: res.headers.get('mcp-session-id') ?? sid, json: line ? JSON.parse(line.slice(5)) : null };
}
const { sid } = await rpc({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {
protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'reach', version: '1' } } });
await rpc({ jsonrpc: '2.0', method: 'notifications/initialized' }, sid);
const { json } = await rpc({ jsonrpc: '2.0', id: 2, method: 'tools/list' }, sid);
const tools = json.result.tools;
console.log(`tools: ${tools.length}`);
for (const t of tools) console.log(' ', t.name);
Its output, on 12 August:
tools: 11
search-actors
fetch-actor-details
call-actor
get-actor-run
get-dataset-items
get-key-value-store-record
abort-actor-run
search-apify-docs
fetch-apify-docs
report-problem
apify--rag-web-browser
Ten of those are the server's own tools. One is an Actor: RAG Web Browser, which belongs to Apify. Of the 23 Actors I have published, zero are in the list.
Nothing is being hidden. Add ?actors= to the same URL and name all 23 slugs. The same server then returns 27 tools: my 23 plus four storage tools. The Actors are reachable. They are not default.
The default is also defensible. The Store holds thousands of Actors. A server that turned every one of them into a tool definition would blow out the context window of every client that connected. Eleven tools is a budget, not a snub.
But it does mean that everything I had written was conditional on a step I had never measured.
Three ways an Actor reaches an agent
| Route | Who decides | What an author can do about it |
|---|---|---|
A — named in the connection URL (?actors= / ?tools=) |
whoever configures the agent | Almost nothing. They have to know your slug before they can type it. |
| B — included in the default set | Apify | Nothing. Today that set contains one Actor. |
C — found at runtime by search-actors
|
Store search ranking | This one. |
A and B are not work I can do. C is the entire surface, so I measured it.
Measuring the third route
The first two attempts failed on the parameter name. {"search": …} and {"query": …} are both accepted and both silently ignored. The server echoes Search query: back empty and returns its default list. Rather than guess a third time, I read the tool's own inputSchema out of the JSON-RPC reply to tools/list above:
keywords : string default ""
limit : integer default 5, max 10
offset : integer default 0
The parameter is keywords. But the line that matters more is limit. It defaults to 5 and caps at 10. That is the window. An agent that runs one search and picks from it is choosing among five Actors out of the whole Store.
The second trap is in the response. Each result set is introduced by a line like **Number of Actors found:** 7, which reads as a total and is not one. It is the size of that page:
-
Instagramkeeps producing fresh results atoffset0, 10, 20 and 30 — 39 distinct Actors, no repeats. One of those four pages came back with nine rather than ten, which is the second thing to know: the pages are ragged. -
Chrome extensionsreturns 7 atoffset0 and a different 6 atoffset10. A short page does not mean the list ended.
My first pass treated the short page as the end of the list, so it never looked past page one. It reported seven of my Actors as absent when they were sitting at ranks 12, 16, 31, 41, 52, 55 and 57. Every number below comes from the corrected run. That run pages to rank 100 whatever the page count says, and computes rank as offset + position in page, because the pages come back ragged.
What it found
I paired each of 23 Actors with the query it was built for — 28 queries in all, since several Actors got both a specific phrase and the broader term a user might type instead. Then I asked how deep in the results that Actor sits.
| Where my Actor lands | queries | share |
|---|---|---|
| inside the default 5 the agent receives | 6 | 21% |
| inside the maximum 10 | 7 | 25% |
| rank 11–100: indexed, past the window | 7 | 25% |
| not in the first 100 | 14 | 50% |
On the queries these Actors were written for, an agent doing a default search finds one of them about one time in five. Half of them are not in the first hundred results at all.
That was worse than I expected, but it was not the finding. The finding is which half.
The queries I can win are the queries nobody is making
Every search-actors result carries the Actor's usage with it, in a line like **Stats:** 20 total users, 3 monthly users. So the demand on a query is readable from the same response that gives you the ranking: take the 30-day user count of whatever sits at rank 1.
| my rank | queries | median 30-day users of that query's rank-1 Actor |
|---|---|---|
| reachable (top 10) | 7 | 6 |
| unreachable (past 10, or absent) | 21 | 23 |
The unreachable side is where the users are. App Store — 311. Google Play — 295. article extractor — 271. Shopify — 186. My Actors for the first three of those are not in the first hundred results.
And the one query where I rank #1 is sitemap checker, where the rank-1 Actor has 0 users in the last thirty days. That Actor is mine. I am first on a shelf with no traffic, and invisible on every shelf with traffic.
I had measured the same shape once before, from the other side. On 2 August I sampled the Store itself and found that Actors naming a well-known platform had a median of 5 monthly users against 2 for the rest. I read that as an argument for naming platforms. This measurement says the platform-named shelves are exactly the ones where I cannot be seen — 1 reachable query out of 14, against 6 out of 14 for the generic ones. Both results are true and they are not in conflict: platform names are where demand collects, demand attracts competitors, and competitors fill a window five deep. But I predicted the wrong one.
The tool tells the agent to broaden the search, and broadening removes me
Every search-actors response ends with this, addressed to the agent:
IMPORTANT: You MUST always do a second search with broader, more generic keywords (e.g., just the platform name like "TikTok" instead of "TikTok posts") to make sure you haven't missed a better Actor.
That is good advice for the user. It is also an instruction to run the query I do worst on. Same Actor, specific phrase against the broadened phrase:
| specific | rank | broadened | rank |
|---|---|---|---|
sitemap checker |
#1 | sitemap |
not in first 100 |
PDF tables |
#2 | PDF |
not in first 100 |
PDF text markdown |
#2 | PDF |
not in first 100 |
Chrome extensions |
#16 | Chrome Web Store |
not in first 100 |
Shopify apps |
#41 | Shopify |
not in first 100 |
GitHub repository |
#52 | GitHub |
not in first 100 |
App Store apps |
absent | App Store |
absent |
Google Play apps |
absent | Google Play |
absent |
Six worse, none better, two unchanged. The clearest one is the first: sitemap checker puts me at #1, and deleting one word puts me past rank 100. Whatever advantage a precise name buys, the agent is under instructions to take a second look without it.
What decides the order
Not popularity, at least not alone. Across every first page I collected, 157 of 238 adjacent pairs are in descending order of monthly users — 66%. A pure popularity sort would be 100%. Relevance carries real weight, which is how an Actor with no users reaches #1 on sitemap checker.
Apify documents what does decide it, and says so for both surfaces at once. From Actor quality score:
Actors with higher quality scores tend to rank higher on both surfaces, though no specific position is guaranteed.
The second surface named on that page is "the Apify MCP server search-actors tool used by external AI agents". The score aggregates eight categories: reliability, popularity, feedback and community, ease of use, pricing transparency, trustworthiness, history of success, and congruency of texts.
I have moved it once and can report the size of the move. On 2 August I added dataset output schemas across the catalogue; the score on the Actor I watched went from 74 to 78, and after that pass the whole catalogue sat between 78 and 81. There is no API for the number — I tried four endpoints and got 404 from each — so the only place to read it is Console > Insights > Actor quality.
Here is that panel today, for the Actor this article keeps returning to:
79 out of 100. Better than 71% of Actors on the platform, better than 99% on reliability, and one suggestion left in the panel.
That is the pairing worth sitting with. Console rates this Actor above 71% of the platform and has one cosmetic item left to suggest. The same Actor is absent from the first hundred results on every query with real demand behind it, and its one first place is on a query with none. Both readings are correct, and the documentation is careful enough to allow it: a higher score tends to rank higher, with no position guaranteed. On a crowded shelf, tending is not the same as landing in the five results the agent receives.
Which leaves the honest part. Three of the eight categories — popularity, feedback and community, history of success — are things you get from having users. For an Actor with none, part of the ranking that would bring users is held shut by not having them yet. That is not a complaint about the design; a store that ranked unproven tools first would be worse for everyone using it. But it is the actual shape of the problem, and no amount of description polishing changes it.
What I would tell an Actor author
- Check what your connection sends before you tune anything. The 20 lines above answer it. If your Actor is not in the list, none of your tool-definition work is reaching an agent yet.
-
Measure your rank on the query you were built for, with
limit: 10, paging past the first short page. Anything past rank 10 is in the index and outside the window. - Read the demand off the same response. The rank-1 Actor's monthly users are printed next to it. If that number is 0, being #1 buys nothing — I have the receipt.
- Expect the broad term to be worse. It is also where the demand is. Decide which of those two facts you are building for, rather than discovering the trade after you publish.
What I got wrong
Three things, in the order I found them.
I narrowed my own MCP connection to one Actor and then wrote six articles about how agents see my catalogue. The narrowing was in a config file I had not opened in months.
I read a page count as a total, and my first pass reported seven Actors as unreachable that rank between 12 and 57. Had I not checked whether offset kept returning results, this article would have carried seven false claims of the most flattering kind — the kind where the platform looks worse than it is.
And I expected the platform-named Actors to be the findable ones. They are the least findable ones I own.
None of that makes the earlier six articles wrong. It reorders them. An input schema that misleads an agent, an output schema that arrives empty, a default that fills a required field — all of those are real, and all of them start to matter at the moment the agent receives the tool. For 23 out of 23 of mine, that moment does not happen by default.

Top comments (0)