To automate list enrichment over MCP, connect an AI agent to a hosted data server, tell it the exact tools and the record budget, and let it work the list row by row. The agent does the looping. You keep the rules.
Most guides stop at "add the URL, then ask nicely". That works for ten rows and fails for a thousand. This one adds the parts a technical buyer needs: a free size check, a budget, a stop rule and a REST fallback.
The setup uses Claude Code and the LeadOcean MCP server at https://api.leadocean.io/mcp. Any client that speaks remote MCP over HTTP works the same way.
TL;DR
- The first run takes about 20 minutes. After that, a new list is one prompt.
- The walkthrough spends about 60 records: one per person returned or enriched. Counts, filter lookups and 404s are free.
- Let the agent plan and size. Do the bulk loop in a script, where rate and cost are fixed.
Prerequisites
- A LeadOcean account from app.leadocean.io. Free is 1,000 records, one-off, no card.
- Claude Code installed. Cursor, Codex, VS Code and others also work.
- For Step 5 only: an API key exported as
$LEADOCEAN_API_KEY, pluscurlandjq. - A CSV of targets. Examples use
acme.comand Jane Doe as placeholders.
export LEADOCEAN_API_KEY="paste-your-key-here"Step 1: Connect the MCP server
MCP signs in with OAuth 2.1, so no key is pasted into the client or the chat log. One URL is the whole configuration.
claude mcp add --transport http leadocean https://api.leadocean.io/mcpOpen Claude Code, type /mcp, pick leadocean and approve the sign-in in the browser. The server exposes 13 tools. You can see and revoke the connection under API keys, then Connected apps, in the app. Source: LeadOcean MCP docs, September 2026.
Step 2: Learn what is free and what spends records
Split the 13 tools into two groups before you write a prompt. The agent should know this too.
| Tool | Cost |
|---|---|
leadocean_count_leads, leadocean_count_companies | Free |
leadocean_list_filters, leadocean_list_enum_values | Free |
leadocean_get_account, leadocean_get_export, leadocean_list_exports | Free |
leadocean_search_leads, leadocean_search_companies | 1 record per row returned |
leadocean_get_lead, leadocean_get_company, leadocean_find_phone | 1 record |
leadocean_export_leads | 1 record per row |
A call that finds nobody costs nothing, the same as a REST 404. One record is one person or company you receive.
Step 3: Size the list for free
Ask the agent to count before it pulls anything. This is the cheapest check you will ever run.
Use leadocean_count_leads with facets=false. Filters: jobLevel VP,
jobFunction "Sales & Business Development", country US,
employeeRange 51-200. Report total, totalAll and distinctCompanies.
Do not call any tool that spends records.The default count covers only mailable people (verified, catch_all_valid or catch_all). totalAll is the same filters with no email condition. Say which one you mean when you report a number.
Enum values are exact strings. If the agent guesses Sales, you get zero rows. Tell it to resolve with leadocean_list_enum_values first.
Step 4: Enrich the list with a budgeted prompt
Give the agent the input, the output, the tools it may use and a hard record budget. A prompt with no budget is a prompt that spends your balance.
Read targets.csv (columns: company, domain).
For each domain, in order:
1. leadocean_search_leads with domain=[<domain>], seniority ["c_suite","vp"],
limit 2. Keep rows whose title matches sales, revenue or growth.
2. For each kept person, leadocean_get_lead with reveal_email=true.
3. Write enriched.csv: domain, full_name, title, email, email_status, person_id.
Rules:
- Call leadocean_get_account first and again every 20 domains.
- Stop and report if records left drops under 100 or any call returns 402.
- Never use leadocean_find_phone or export tools in this run.
- Keep only email_status verified, catch_all_valid or catch_all.
- If a search returns nobody, write the domain to misses.csv and move on.Search returns a thin row with a person_id and flags, not the address. The address comes from get_lead with reveal_email. There is no name-plus-domain lookup, which is why the prompt searches by domain first.
Start with 10 domains. Read enriched.csv, then run the rest.
Step 5: Move the bulk loop to a script
For hundreds of rows, use REST instead. A script has a fixed rate and a fixed cost per row. An agent has neither. If your list is LinkedIn URLs, enrich by linkedin_url.
One LinkedIn URL per line in targets.csv, no header.
while read -r url; do
curl -s https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"linkedin_url\": \"$url\", \"reveal_email\": true}" \
| jq -c '{url: "'"$url"'", status: .data.contact_data.email_status, emails: .data.contact_data.contact_emails}'
sleep 0.25
done < targets.csv > enriched.jsonlEnrich takes exactly one of linkedin_url, email, phone or person_id. The sleep 0.25 keeps you under 5 requests a second on Free. A person we hold nothing for is a 404 and costs nothing.
Since October 2026 the enrich response also carries career history under resume_data: past roles, education, certifications and awards. Past employers have no company_id yet.
For a filter-defined list rather than a known one, skip both routes. POST /v1/exports takes the people-search filters, up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free. The Exports page in the app shows the record price before you start.
Step 6: Check the output before you send
Read email_status on every row. LeadOcean gives no refund for bounces, so the filter in the prompt is your protection.
jq -c 'select(.status | IN("verified","catch_all_valid","catch_all"))' enriched.jsonl \
| wc -lcatch_all means the domain accepts everything and the mailbox is unproven. Send those in a smaller, separate batch.
Step 7: Make the run repeatable
An agent run you cannot repeat is a one-off. Save the prompt from Step 4 in your repo as enrich-prompt.md and pin three things in it: the tools allowed, the record budget and the output columns.
Keep person_id in every output row. It is the stable key. On the next run, skip any domain already present in enriched.csv, so a restart does not pay twice for the same person.
Treat the agent's output like any other data pipeline output. Diff it against the last run, spot-check ten rows by hand, and read misses.csv. A high miss rate usually means the input domains are wrong, not that the data is missing.
Two limits are worth writing into the prompt. One search pages to 10,000 rows, and meta.depthCapped tells you when it stopped. And search returns people at the domain, so a domain with no one at VP level returns nothing. Loosen seniority to director before you decide the company is empty.
What the agent is good at here
The agent earns its place in three jobs. It turns a vague brief into valid filters by resolving enum values. It sizes the market before spending. It handles the messy rows: a parked domain, a subsidiary, a title that says "Head of Growth" instead of "VP Sales".
It is a poor loop runner. It spends more tokens per row than a script, it can retry, and it decides its own call count. That is why Step 4 caps the run and Step 5 moves volume to curl.
What you get
Step 3 is free. We ran leadocean_count_leads on 2026-10-21 with jobLevel VP, jobFunction Sales & Business Development, country US and employeeRange 51-200, facets off.
| Measure | Value |
|---|---|
| Mailable people (default filter) | 18,567 (exact) |
| Same filters, no email condition | 60,456 |
| Distinct employers spanned | 12,192 |
| Records spent | 0 |
That is a market, not a list to enrich in one pass. Narrow by industry or add domain before you spend.
An enriched person comes back in this shape. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": {
"profile_data": { "profile_full_name": "Jane Doe" },
"contact_data": {
"has_email": true,
"email_status": "verified",
"contact_emails": [
{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }
]
}
}
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | The x-api-key header is missing, or the key is wrong or revoked. | Copy a fresh key from the app and reload $LEADOCEAN_API_KEY. |
| 402 | Your records are spent. On Free the 1,000 are one-off. | Check leadocean_get_account or GET /v1/account, or move to a paid plan. |
| 429 | Requests above your plan's rate limit: 5 a second on Free, 200 on Agency. | Wait the Retry-After seconds. Add a sleep to the loop. |
| Empty results | A filter value is not an exact enum string, such as Sales for Sales & Business Development. | Call leadocean_list_enum_values and pass the exact string. |
Search stops, meta.depthCapped true | One search pages to 10,000 rows. | Split by country, industry or headcount band and run several searches. |
| Export refused | One export is capped at 50,000 rows a request and 500,000 a day. | Split the job across requests and days, or narrow the filters. |
FAQ
Should the agent or a script do the enrichment?
Use the agent to size, plan and handle the odd cases. Use a script for volume. A script has a fixed rate and a cost you can compute. An agent decides its own call count.
How many people can I enrich on the free plan?
You have 1,000 records, one-off. A person found by search and revealed costs about 2 records, so roughly 500 people. Enrich by linkedin_url and it is 1 record each.
Does the agent ever see my API key?
Not over MCP. The connection uses OAuth 2.1, and you can revoke it under Connected apps. The REST loop in Step 5 does use a key, which stays in your shell.
Does a paid plan change the workflow?
The data and features are the same. Solo ($97 a month) and Agency ($497 a month) use a fair-use pace instead of credits, and Agency allows 200 requests a second. See pricing.
For the wider picture, read data enrichment for developers and API enrichment and automated prospecting. The term itself is defined in the enrichment glossary. More recipes live in the blog hub.
Connect the MCP server and size your list free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →