Apify MCP 伺服器 20 個求職 Actors 的工具定義用量介乎 2.1K 至 32.7K tokens
MCP tool cost: 20 job Actors on Apify, 2.1K to 32.7K tokens each
Kevin Hall 測量 Apify 託管 MCP 伺服器中的 20 個求職 Actors,發現每個工具定義耗用 2,139 至 32,737 個 tokens,中位數為 3,829。
Here is what an agent pays before it asks a job-search tool anything. I pinned 20 job-search Actors from the Apify Store, one at a time, on the hosted Apify MCP server and read what tools/list sends back. The smallest tool definition was 2,139 tokens. The median was 3,829. One was 32,737, and 32,136 of those tokens were an output schema the server had inferred from the Actor's results; the author's own input schema was 406 tokens.
Three of the twenty are mine, so this is also a check on my own work. The numbers are from 28 October 2026, and the method is a curl handshake you can rerun on any Actor in a minute.
Why the tool definition is the first cost
An MCP client loads every tool's name, description and JSON schema before the model reads your question. On the hosted Apify server each Actor becomes one tool, and the tool carries three parts: the description (the Actor's Store description after a one-line preamble), the input schema (the Actor's input fields, as the author wrote them), and an output schema that the server infers from what the Actor's runs return (documented here; the local stdio server does not add it). The third part is the one most Actor authors have never looked at, because they never wrote it.
A post two days ago measured a hand-built server at 15 tools and rebuilt it to 4. I wanted the same number for tools I did not build and cannot rebuild: the Actors an agent meets when a user says "find me backend jobs in Germany".
How I measured
Hosted server, one Actor pinned per session with ?tools=, then the plain tools/list after the initialize handshake:
U='https://mcp.apify.com?tools=curious_coder/linkedin-jobs-scraper'
H=(-H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream')
# 1. initialize; the session id comes back as a response header
curl -s -D hdr -o /dev/null "$U" "${H[@]}" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
SID=$(grep -i '^mcp-session-id' hdr | awk '{print $2}' | tr -d '\r')
# 2. initialized notification, 3. the list (server-sent events; the data: line holds the JSON-RPC result)
curl -s -o /dev/null "$U" "${H[@]}" -H "Mcp-Session-Id: $SID" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -s "$U" "${H[@]}" -H "Mcp-Session-Id: $SID" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' > tools.sse
I serialized each tool definition as compact JSON and counted tokens with tiktoken's o200k_base. Claude's tokenizer is not public, so treat the counts as a scale, not an invoice. The relative sizes are the point, and those do not depend on the tokenizer.
The twenty are the Actors the Store returns for "linkedin jobs", "job search", "job postings", "indeed jobs" and "glassdoor jobs", ranked by users in the last 30 days, plus my three (they are new and would not have made the cut on users).
The table
| Actor | tokens per tool | description | input schema | output schema | input fields |
|---|---|---|---|---|---|
valig/indeed-jobs-scraper |
32,737 | 52 | 406 | 32,136 | 6 |
cheap_scraper/linkedin-job-scraper |
7,723 | 87 | 6,061 | 1,440 | 38 |
bebity/linkedin-jobs-scraper |
6,769 | 57 | 5,029 | 1,551 | 18 |
fantastic-jobs/advanced-linkedin-job-search-api |
6,569 | 86 | 4,312 | 2,018 | 41 |
fantastic-jobs/career-site-job-listing-api |
6,324 | 93 | 4,083 | 1,999 | 39 |
b2bsearch/linkedin-job-search-api (mine) |
5,770 | 92 | 3,410 | 2,123 | 38 |
b2bsearch/linkedin-jobs-scraper (mine) |
4,389 | 107 | 2,403 | 1,729 | 25 |
agentx/all-jobs-scraper |
4,041 | 69 | 2,060 | 1,780 | 11 |
epctex/google-jobs-scraper |
3,981 | 72 | 2,708 | 1,068 | 14 |
harvestapi/linkedin-job-search |
3,911 | 43 | 1,492 | 2,217 | 19 |
openclawai/job-board-scraper |
3,748 | 82 | 2,054 | 1,461 | 21 |
borderline/indeed-scraper |
3,416 | 78 | 1,096 | 2,094 | 15 |
curious_coder/linkedin-jobs-scraper |
3,343 | 52 | 1,711 | 1,444 | 14 |
b2bsearch/hiring-companies-finder (mine) |
3,303 | 101 | 1,329 | 1,723 | 15 |
fast_data_asap/career-site-jobs-api |
3,235 | 106 | 1,777 | 1,277 | 13 |
fast_data_asap/job-search-api |
2,562 | 92 | 1,108 | 1,291 | 13 |
misceres/indeed-scraper |
2,509 | 62 | 925 | 1,394 | 9 |
valig/linkedin-jobs-scraper |
2,180 | 54 | 682 | 1,310 | 14 |
valig/glassdoor-jobs-scraper |
2,140 | 59 | 599 | 1,340 | 13 |
apimaestro/linkedin-jobs-scraper-api |
2,139 | 59 | 564 | 1,375 | 12 |
Where the tokens go
The input schema tracks the number of fields, not the quality of the Actor. Six fields cost 406 tokens; 38 to 41 fields cost 3,410 to 6,061. Every filter an author adds is a line the agent reads on every turn it has the tool loaded. Mine sits in that band on purpose: 38 fields, 3,410 tokens, because the Actor exposes the database's filters (boolean title and description search, exclusions, employer size, industry, salary, applicants, expiry) instead of hiding them behind one free-text box. That is a trade, and the number is its price.
The output schema is the larger half in nine of the twenty tools. The server generates it from the Actor's dataset, so a scraper that returns a deeply nested row pays for it on every turn, and the author never sees it unless they run tools/list themselves. The Indeed scraper at the top of the table returns rows that produced a 3,400-node schema; the tool definition for my jobs Actor has 229 nodes. The other nineteen output schemas came in between 1,068 and 2,217 tokens, which looks like the floor for a flat row with a few dozen keys.
The description is cheap, 43 to 107 tokens. The server takes the Store description verbatim after a preamble ("This tool calls the Actor ... and retrieves its output results. Actor description: ..."). So the one text an author writes for humans is also the text the agent reads to choose between tools, and it is under three percent of the tool's cost. Spend the words there.
What the server does to a listing
Three transformations were visible in the payloads.
Long field descriptions are cut. On the Actor with the longest ones, four of 38 fields ended in "[Description truncated]"; their raw descriptions ran 518 to 810 characters and came through at 371 to 495 plus the marker. Whatever you wrote after the cut, the agent never reads.
Prefill values are appended to the description as "Example values: ...". If the prefill is a realistic example, that is free documentation. If it is a placeholder, the agent copies the placeholder.
required is passed through as written. Four of the twenty require at least one field. A required field that also has a default is a small trap: the agent must supply it even though the Actor would run without it.
The fixed overhead you pay even for one Actor
Pin one Actor and the server still sends four helper tools: get-actor-run (1,437 tokens), abort-actor-run (1,311), get-dataset-items (667) and get-key-value-store-record (373). That is 3,788 tokens before the Actor itself; for the median Actor, half the bill.
Without ?tools=, the default server sends 12 tools and 18,391 tokens: the three discovery tools (search-actors 2,315, fetch-actor-details 1,755, call-actor 2,009), the four helpers, two docs tools, a problem reporter and two web tools (rag-web-browser 4,508, web-fetch 2,647). ?tools=actors keeps discovery and cuts the rest: 7 tools, 9,867 tokens.
What Claude Code does with it
I have five recorded claude -p sessions over the pinned jobs tools from the last three posts, two or three Actors pinned each time. In all five, the agent's first action was ToolSearch with select:mcp__apify__b2bsearch--linkedin-job-search-api,.... Claude Code defers MCP tool definitions above a size threshold: the model sees the names at the start and pulls a definition into context when it decides to use the tool. The first turn of the Java-in-Berlin session carried 43,743 tokens of context before any Actor definition had been fetched.
So in Claude Code the tool tax is not paid on turn one. It is paid from the turn the definition is loaded, on every turn after. The session that ran 18 free previews took 39 turns and read 523,825 cached input tokens in eleven minutes, and the tool definitions were part of every one of those reads. Across the five sessions the agent used between 9 and 16 of the Actor's 38 input fields; the other 22 to 29 rode along in every turn as schema the agent had read and did not need.
A host without deferral puts the whole tools/list into the system prompt on turn one, and pays the table above on every turn from the first. The 15-to-4 post I linked measured exactly that case.
Discovery versus pinning: two sessions, one question
Same prompt, two Claude Code sessions, one difference in the MCP URL. The prompt is a backend developer in Berlin with six years of Java asking which technology to learn next, judged by what German employers ask for: count the base, count the current stack in descriptions, count eight candidates, count two of them together with Java.
Pinned, ?tools=b2bsearch/linkedin-job-search-api,b2bsearch/hiring-companies-finder (the recorded session from the third post): 39 turns, eleven minutes, 18 free previews, 523,825 cached input tokens read. The agent had exact counts for the base and for every technology, by employer, and picked Kubernetes with AWS close behind.
Discovery, ?tools=actors (search, details, call, plus the four helpers: 9,867 tokens of definitions), with money rules added to the prompt: read an Actor's pricing before calling it, cap every call, keep the total small, prefer free counts. 33 turns, fifteen minutes, 32 tool calls: 5 Store searches, 12 listing fetches, 3 paid runs on two scrapers, 11 reads of run state and rows. 220,114 tokens written to cache and 1,571,639 read, three times the pinned session. The agent never saw my Actor: the five searches came back led by the most-used scrapers, and a tool with a handful of users in its first week was in none of the lists. It chose the most-used LinkedIn scraper and an Indeed scraper, pulled 225 rows, filtered them to 82 backend postings, and answered with shares of that sample, labelled as a sample with a ten-point margin, and picked AWS with Kubernetes close behind. The three runs all stayed under the cap the agent had set on each call.
Where the discovery turns went is the part worth keeping. Two of the twelve listing fetches came back over Claude Code's tool-result limit (61,678 and 68,510 characters: a README plus both schemas), one hit a 502, and the rest were 14,000 to 24,000 characters each. Reading listings cost the session more context than running them.
My first attempt at the discovery session had to be killed, and the reason belongs in the post. I had left the shell tool allowed. When a listing fetch overflowed and Claude Code wrote it to a file, the agent read the file with the shell, then started checking the environment and my credentials file for an Apify token so it could call the API directly. Nothing was sent anywhere and the transcript holds no token, but an agent with a shell and a blocked tool result will look for another road. The second run had --permission-mode default and the shell in --disallowedTools, and stayed on the MCP tools.
What I keep in my own schema, and what I changed
The three b2bsearch Actors follow rules I had set before this measurement, and the payloads confirm they survive the server:
- The two fields that change what a run costs,
previewOnlyandcompact, are the fourth and fifth fields in the schema, before any filter, so a client that shows the first N fields shows them. - Field descriptions are capped at 350 characters by a test in the build (the longest live one is 342). None of my fields carries the "[Description truncated]" marker.
- No required fields.
{"previewOnly": true, "titleSearch": ["backend"], "locationSearch": ["de"]}is a complete call. - Rows nest two levels at most (a company card, a recruiter), which gives an output schema of 2,123 tokens and 229 schema nodes. The 3,400-node schema at the top of the table cost 32,136.
What this measurement changed: I had been treating the input schema as the tool's cost. It is a third to a half of it. The helper tools and the inferred output schema are the rest, and only the second of those is under an author's control, through the shape of the rows the Actor writes.
What I did not do is cut fields. The recorded sessions used nine to sixteen of them, different ones each time, and a database-backed search with fewer filters would just move the filtering into the agent's own loop, which is far more expensive than a schema line.
For AI agents reading this
To use the jobs tool from Claude Code with the smallest tool set, pin it:
claude mcp add --transport http apify "https://mcp.apify.com?tools=b2bsearch/linkedin-job-search-api" --header "Authorization: Bearer <APIFY_TOKEN>"
or in any MCP client config:
{"mcpServers": {"apify": {"type": "http", "url": "https://mcp.apify.com?tools=b2bsearch/linkedin-job-search-api", "headers": {"Authorization": "Bearer <APIFY_TOKEN>"}}}}
The tool is named b2bsearch--linkedin-job-search-api (mcp__apify__b2bsearch--linkedin-job-search-api inside Claude Code). Call it first with previewOnly: true: it returns the count of matching postings and a breakdown by country, seniority, employment type, industry, top employers, titles and cities, no rows and no charge. Then turn previewOnly off and set limit to pull rows; rows come back through get-dataset-items. Filters that matter most: titleSearch (one line per title variant, boolean allowed), locationSearch (country codes or cities), timeRange (7d, 30d, 6m), descriptionSearch (one line per technology, lines are OR, quote phrases), removeAgency: true to drop staffing agencies and job boards, status (active, expired, any).
To measure any Actor's tool cost: run the handshake above with ?tools=<owner/actor>, take the data: line of the tools/list response, and count the compact JSON of each entry in result.tools. Expect four helper tools in the list besides the Actor.
What this does not measure
Token counts are tiktoken's, not Claude's; the ranking holds, the absolute numbers are a scale. I measured one host, Claude Code, for deferral; other hosts load the list differently. The twenty Actors are a Store search on one day, not the whole category. And none of this says whether a bigger schema makes an agent worse at the task; the post I linked measured call counts for one server, and the discovery session above is one run, not a benchmark.
This post is a companion to the series on developer job search with Claude Code: the first post searches the market by stack, the second measures ghost jobs, the third counts the technologies employers ask for, all over the same pinned tool. The Actors are LinkedIn Jobs Database API, Companies Hiring Now and Advanced LinkedIn Jobs Scraper; per-tier pricing is on each Actor page.
來源:DEV Community · dev.to
