HubSpot to LinkedIn Ads, from your terminal.
Adwarp is an open-source MCP server and CLI that turns a HubSpot list or a CRM search into a LinkedIn matched audience. It reads with a read-only private app token, and only hashed emails leave your machine.
$ claude mcp add adwarp -- npx -y adwarp mcp$ npx adwarp audience from-hubspot --dry-run -n "Open deals" --list 123
every member of the list read, with a read-only token
records read, valid and skipped, with the reason for each skip
5 cleaned rows, emails masked next to their SHA-256 hashes
Nothing uploaded. Run it again without --dry-run to upload.
01What you can ask
What you can ask. Real requests, answered from your terminal.
$ claude
> Build an audience from our HubSpot list of open deals.
adwarp.audience_from_hubspot open deals list, dry run: counts and a masked sample
Contact list, matched on email. Nothing uploaded yet.
adwarp.audience_from_hubspot same list: contact list
Only the email hashes uploaded. Matching now, ready in up to 48 hours.
02How it works
How it works.
Create a private app
In HubSpot, create a private app with 3 read-only scopes: contacts, companies and lists.
Save the token
Put its token in ~/.adwarp/hubspot.json or the ADWARP_HUBSPOT_TOKEN variable, on your machine.
Ask or run it
Name a HubSpot list or describe the records. Adwarp previews first, then hashes the emails and uploads.
03Results
Why it gets better results.
Current on launch day
Audiences come straight from the lists you keep in HubSpot, so they match your pipeline the day you launch.
No export step
No list export, no CSV cleanup and no file left in your downloads folder.
Read-only by design
The token only needs read scopes, and Adwarp never writes to HubSpot.
Is my data sent anywhere?
Only to LinkedIn. Adwarp reads HubSpot on your machine, and for contacts it uploads only SHA-256 hashes of the emails. Company lists upload names and websites.
Can I see what will upload first?
Yes. A dry run reads HubSpot and shows the counts and 5 cleaned rows, with emails masked, and uploads nothing.
Does it keep the audience in sync?
Not on its own. Each run builds a fresh audience, so rerun it on a schedule, with cron or a scheduled agent, and swap the new one into targeting.
Does it work on the hosted endpoint?
No. A token stored on a shared server would let any caller read that HubSpot account, so it runs through the local MCP server or the CLI.
Which HubSpot scopes does it need?
3 read-only scopes on a private app: crm.objects.contacts.read, crm.objects.companies.read and crm.lists.read. Adwarp only reads, and never writes to HubSpot.
In HubSpot, go to Development, then Legacy apps, and create a private app. Creating one takes a super admin. On the Scopes tab, add these 3 and no others, then copy the token from the Auth tab.
| Scope | What it reads |
|---|---|
| crm.objects.contacts.read | Contact emails |
| crm.objects.companies.read | Company names and domains |
| crm.lists.read | The members of a list |
{ "accessToken": "your-private-app-token" }chmod 600 ~/.adwarp/hubspot.jsonCan I build an audience from a list or a search?
Either: a list's members, or a search with up to 6 property filters that all match.
A list builds the audience its members make up, contacts or companies. A search builds a contact audience unless you pass -t company. Each --filter is property:operator:value, with the property's internal name in HubSpot.
npx adwarp audience from-hubspot -n "Open deals" --list 123 --dry-run
npx adwarp audience from-hubspot -n "Marketing qualified leads" \
--filter lifecyclestage:EQ:marketingqualifiedlead \
--filter "email:CONTAINS_TOKEN:*@acme.com" --dry-run
npx adwarp audience from-hubspot -n "Large accounts" -t company \
--filter annualrevenue:GT:10000000 --dry-runWhich lists are worth building?
Open deals, marketing qualified leads, customers and target accounts.
| List | What it's for |
|---|---|
| Open deals | Reach the buying group while the deal is open |
| Marketing qualified leads | Keep warm leads seeing your ads |
| Customers | Exclude them from prospecting campaigns |
| Target accounts | Feed 1:1 account-based ads as a company list |
Which tools and commands does it use?
3 MCP tools and 3 CLI commands, with what each one does.
| Tool | What it does |
|---|---|
audience_ | Build a matched audience from a HubSpot list or search |
get_ | Check how the list is matching |
update_ | Swap a fresh audience into an ad group's targeting |
CLI
npx adwarp audience from-hubspot -n <name> --list <id> --dry-runnpx adwarp audience from-hubspot -n <name> --filter <prop>:<op>:<value> -t contact|companynpx adwarp audience status <segmentId>
What are the limits?
6 things to know before you start.
- Runs locally through the MCP server or the CLI, not on the hosted endpoint.
- A search reads at most 10,000 records. For more, save them as a HubSpot list and use --list.
- Up to 6 filters per search, and every one must match. For OR logic, build a list in HubSpot.
- Contacts match on their primary email only.
- Each run creates a new audience, and the change journal doesn't record audience creation.
- LinkedIn needs about 300 matched members before an audience serves, and uploads need its Audiences product on your developer app.