Leadgen 2.0 調整 MCP 伺服器用量計算,改按回應而非請求計量
Metering an MCP server by answers, not requests: what Leadgen 2.0 changed
Leadgen 2.0 本週上線,改為只有返回資料或真正的未找到結果才計入 MCP 伺服器用量;initialize、tools/list、ping、通知、輸入錯誤及拒絕均不扣額度。
Every MCP server that gets listed in a directory learns the same lesson within a week: the most frequent caller is not a customer. It is a crawler doing initialize followed by tools/list, deciding whether you deserve to be indexed, and moving on.
Leadgen 2.0 went live this week. It is not a price change and not a promo — it is the list of things that got in our own way when we used the platform, fixed.
The most interesting one is the metering, because it is a category of bug every MCP server will hit.
Probing should not spend a lookup
Our allowances went up in this release: a free key now gives 50 lookups a day (it was 25), and without a key the MCP server answers 30 calls a day per IP (it was 5), with the REST door counted separately at 5. The two doors never spend each other's allowance, and every limit message quotes the door it came from.
The part that needed fixing was what counts as a call: the counter was incremented on every request to /mcp, including the ones that never reach a tool.
Two days of production logs, same window:
2,333 requests to /mcp ~100 distinct client IPs
612 Uptime-Kuma /health
390 mcpbeat/0.1 directory crawler
339 SentinelOracle/0.1 rank probe
246 curl/7.88.1
112 mcptry.com indexer
64 AgentRankProbe/0.1
and, in the usage table: 4 quota rows at 0 calls, 1 real keyed lookup
A crawler doing initialize + tools/list and nothing else was opening the counter. A day full of robots could leave a real visitor without their free lookups — on a shared egress IP, they would even collide with each other.
The rule we landed on: only answers that carry data (or a genuine not-found) count. The fix is a rule, not a heuristic: only answers that carry data (or a genuine not-found) count. Everything else — initialize, tools/list, ping, notifications, input errors, refusals — is handed back. One owner for the counter (record_call()), one function for the give-back (refund_call()), and the enforcement point decides by the shape of the answer.
We deliberately did not key it on the User-Agent. A rule like "don't charge mcpbeat" is a rule you rewrite every month, and it teaches nothing: the next crawler is one header away from looking like a browser. Meter the protocol, not the caller.
Passwordless sign-in that survives mail scanners
Email plus a 6-digit code, in the app and on the site. No password to reset, no "confirm your email" step that fights with link prefetching.
The trick is that the mailed link only opens a page — it never confirms anything by being fetched. The address is confirmed by that page's POST, so a scanner that walks every link in an inbox confirms nothing. Same pattern we use for watchlist alerts: pending → confirmed on a form submit, never on a GET.
An expired or already-used sign-in link now says so in plain words and offers a fresh one to the same address. The API key waits behind the same door: masked, with a copy button, and replaceable without losing plan, usage or watchlists. That last part is a database decision, not a UI one — the key is a row's identity, not the account.
One search box
Company name, registration number (CUI), or website, in the same input. The front end classifies what you typed and routes it; the API keeps its explicit parameters. Users do not read parameter names, and a search box that requires you to know which field you are filling in is a form wearing a search box costume.
Phone numbers, written the way you dial them
Formatted to dial (0744 700 293), with an international column. Each number carries its provenance — declared to ANAF, found on the company's own site, or listed elsewhere — and a number found on a website is rated high only when it matches the one the company declared. In a registry with millions of rows, "we have a number" and "this number is still that company's" are different claims, and the UI should not blur them.
Big lists: compute in the database, not in the page
Revenue and headcount bands are computed in the database, not in the page, so lists of thousands of companies stop being a progress bar. The bands no longer overlap either — a company lands in exactly one.
Developer door and user door
On the site, "Start free" goes to the app and "Get the free key" goes to the developer page — which is what /start now is. The MCP server carries 18 tools, and scripts can still self-serve a key with POST /register. Machines and people have different first clicks; pretending otherwise serves neither.
Try it in one call
Streamable HTTP is a stateful protocol: you initialize, keep the session id, then call. Two requests, no key:
# 1. open a session — the reply header carries Mcp-Session-Id
curl -s -D - https://leadgen-mcp.adrianhomelab.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# 2. call a tool with that id (a real one: this CUI is my own company)
curl -s https://leadgen-mcp.adrianhomelab.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Session-Id: <id from step 1>' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"lookup_business","arguments":{"query":"55685084"}}}'
{"query":"55685084","total":1,"results":[{"companyName":"OAIDA A. IOAN-ADRIAN
PERSOANĂ FIZICĂ AUTORIZATĂ","cui":"55685084","registrationCode":"F2026045821000",
"legalForm":"PFA","county":"Bucureşti",...}]}
Eighteen tools sit behind that door — lookup_business, lookup_companies,
find_company_by_domain, lookup_financials, extract_contacts, watch_company,
poll_watchlist, get_usage and the rest. Docs: https://leadgen.adrianhomelab.com/en/docs.
What 2.0 does not fix yet
Being honest about the tail, because these are the parts still on the board:
-
Authorization: BearerandX-API-Keyboth work, but the docs do not say so — a cold caller guesses. - A request for
/docswith noAccept-Languagestill defaults to Romanian. English lives at/en/docs. - The anonymous wall is per source IP, so callers behind one egress share a bucket. That is a design limit, not a bug we can paper over.
If you build an MCP server, the metering item is the one I would copy first. It is a small amount of code and it decides whether your free tier is a loss leader or a denial of service against your own users.
Leadgen serves Romanian company data over the ONRC registry and ANAF filings — 4.2M companies, lookups by CUI, name or website, from Python, curl or an agent. Prices are charged in euro; on the Romanian pages the amount in lei is shown next to them at the National Bank of Romania's reference rate, with the rate's date. Server: https://leadgen-mcp.adrianhomelab.com/mcp.
More from me
- Romania's company registry, now callable from Claude and Cursor (MCP + REST)
- Romania's company registry has no free API — so I built one over 4.2M firms
- Building a Romanian Business Registry API: 4.2M companies as queryable JSON
- I stopped scraping business directories and built an MCP server on official registry data
來源:DEV Community · dev.to