| name | hubspot |
| description | HubSpot CRM automation: MCP server setup, contacts/deals/tickets via API, data enrichment pipelines, CRM workflows — the official Anthropic-HubSpot integration |
HubSpot Skill
When to activate
- Setting up the official HubSpot MCP server for Claude Code
- Reading or writing HubSpot CRM data (contacts, companies, deals, tickets, notes)
- Building a lead enrichment pipeline that populates HubSpot records
- Automating deal stage updates based on external triggers
- Creating contacts and notes from research or meeting outputs
- Running CRM hygiene: deduplication, missing fields, stale records
When NOT to use
- Salesforce CRM — different API and object model
- Simple Stripe/payment data — use the Stripe skill
- When you need real-time event webhooks from HubSpot — use a webhook receiver endpoint
Instructions
Setup — HubSpot MCP Server (official)
HubSpot has an official MCP server. There are three ways to connect:
Option 1: OAuth connector (simplest — Claude.ai desktop)
- Claude.ai → Settings → Integrations → HubSpot → Connect
- Authenticate with your HubSpot account
- Claude can now read/write your CRM natively
Option 2: MCP server (Claude Code — programmatic)
{
"mcpServers": {
"hubspot": {
"command": "npx",
"args": ["-y", "@hubspot/mcp-server"],
"env": {
"HUBSPOT_ACCESS_TOKEN": "${HUBSPOT_ACCESS_TOKEN}"
}
}
}
}
Get your access token: HubSpot → Settings → Integrations → Private Apps → Create a private app. Scopes needed: crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.companies.read, crm.objects.deals.read, crm.objects.deals.write.
Option 3: Direct HubSpot API (scripts/automations)
import { Client } from '@hubspot/api-client'
const hubspot = new Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN })
Available MCP tools (via the MCP server)
| Tool | What it does |
|---|
search_contacts | Search contacts by email, name, company, or properties |
get_contact | Get full contact record by ID |
create_contact | Create a new contact with properties |
update_contact | Update contact properties |
search_companies | Search companies by name, domain, industry |
create_company | Create a new company |
get_deals | List deals, filter by stage or owner |
create_deal | Create a new deal with associations |
create_note | Add a note to a contact, company, or deal |
create_ticket | Create a support ticket |
Common patterns
Create or update a contact (upsert):
async function upsertContact(email: string, props: Partial<Contact>) {
const existing = await hubspot.crm.contacts.searchApi.doSearch({
filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: email }] }],
properties: ['email', 'firstname', 'lastname', 'company'],
limit: 1,
})
if (existing.results.length > 0) {
return hubspot.crm.contacts.basicApi.update(existing.results[0].id, { properties: props })
}
return hubspot.crm.contacts.basicApi.create({ properties: { email, ...props } })
}
Log a meeting note to a contact:
async function logMeetingNote(contactId: string, summary: string) {
return hubspot.crm.objects.notes.basicApi.create({
properties: {
hs_note_body: summary,
hs_timestamp: Date.now().toString(),
},
associations: [{
to: { id: contactId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 202 }],
}],
})
}
Bulk enrich contacts from research:
async function enrichContactsFromResearch(leads: LeadData[]) {
for (const lead of leads) {
await upsertContact(lead.email, {
firstname: lead.firstName,
lastname: lead.lastName,
company: lead.company,
jobtitle: lead.title,
phone: lead.phone,
hs_lead_status: 'NEW',
lifecyclestage: 'lead',
})
await new Promise(r => setTimeout(r, 100))
}
}
Update deal stage:
async function advanceDealStage(dealId: string, stage: string) {
return hubspot.crm.deals.basicApi.update(dealId, {
properties: { dealstage: stage },
})
}
Search for stale contacts (CRM hygiene):
async function findStaleContacts(daysInactive = 90) {
const cutoff = Date.now() - daysInactive * 24 * 60 * 60 * 1000
const results = await hubspot.crm.contacts.searchApi.doSearch({
filterGroups: [{
filters: [
{ propertyName: 'hs_last_sales_activity_date', operator: 'LT', value: cutoff.toString() },
{ propertyName: 'lifecyclestage', operator: 'EQ', value: 'lead' },
],
}],
properties: ['email', 'firstname', 'lastname', 'hs_last_sales_activity_date'],
limit: 100,
})
return results.results
}
Rate limits
| API tier | Limit |
|---|
| Free/Starter | 100 requests/10 seconds |
| Professional/Enterprise | 150 requests/10 seconds |
| Burst | Up to 200 requests/second briefly |
Always add await new Promise(r => setTimeout(r, 100)) between bulk operations.
Via Claude Code (natural language with MCP)
Once the MCP server is connected, you can tell Claude:
Find all contacts at Acme Corp and add a note saying we discussed their Q3 expansion plans.
Create a new deal for alice@company.com at the Proposal stage, $25,000, closing next month.
Show me all deals in the negotiation stage that haven't been updated in 2 weeks.
Example
User: Build a script that takes a list of CSV leads (name, email, company, LinkedIn URL) and creates/updates HubSpot contacts, logging a note with the source of each lead.
Expected output:
scripts/import-leads.ts — reads CSV, calls upsertContact() for each row
upsertContact(email, props) — search + create/update pattern
logNote(contactId, 'Imported from LinkedIn campaign — 2026-05-20') after each upsert
- Rate limiting: 100ms delay between contacts
- Error handling: log failed rows to
failed-imports.csv for retry