HubSpot Integration - Team Setup Guide
Last Updated: April 1, 2026
Status: Production Ready
This guide helps Brainforge team members access HubSpot with the right default: use the HubSpot app first, then HubSpot MCP as a read-only fallback in Cursor, and use the API service only for programmatic writes or automation.
Quick Start (5 minutes)
Prerequisites
- Access to Brainforge 1Password vault
- Cursor IDE installed
- Node.js 18+ installed
- 1Password CLI installed (
brew install --cask 1password-cli)
Step 1: Start in the HubSpot app
Use the HubSpot app by default for:
- Reviewing pipeline state
- Inspecting deal, company, and contact records
- Manual CRM edits by humans
Only continue to MCP or API setup if you want Cursor-based lookup or automation.
Step 2: Get Credentials from 1Password
# Sign in to 1Password CLI (first time only)
op signin
# Verify you can access the credentials
op read "op://Personal/Hubspot Brainforge Development App/notesPlain" | head -51Password Items Required:
| Item | Vault | Purpose |
|---|---|---|
Hubspot Brainforge Development App | Personal | API Service (read/write) |
Hubspot MCP | Personal | MCP Server (read-only) |
Step 3: Set Up the API Service
# Navigate to the API service
cd integrations/hubspot/api-service
# Install dependencies
npm install
# Create .env file with 1Password token
echo "HUBSPOT_ACCESS_TOKEN=$(op read 'op://Personal/Hubspot Brainforge Development App/credential')" > .envStep 4: Set Up HubSpot MCP in Cursor
The MCP is a read-only fallback for Cursor. It is already configured globally. After restarting Cursor, it should auto-connect.
To verify MCP is working, ask in Cursor:
"Show me all deals for Inteleos"
What You Can Do
HubSpot app (default)
The HubSpot app should be the team’s primary access path:
- Open records directly in HubSpot for the current source of truth
- Use the UI for ordinary human review and edits
- Validate pipeline status there before using CRM values in delivery planning
Natural Language Queries (via MCP fallback - Read Only)
The HubSpot MCP allows you to query HubSpot data using natural language in Cursor when the app is inconvenient or the user wants in-chat retrieval:
"Show me all deals for [company name]"
"Get contact details for [person name]"
"What deals are in the proposal stage?"
"Who are the contacts at [company]?"
Programmatic Operations (via API Service - Read/Write)
The API service is for deliberate automation and agent-driven writes:
import {
// Companies
getCompany, getCompanyByName, getCompanyByDomain, searchCompanies,
// Contacts
getContact, getContactByEmail, getContactByName, searchContacts,
// Deals
getDeal, searchDeals, updateDeal, updateDealStage, createDeal, addNoteToDeal,
// Associations
getDealWithAssociations, traverseDealAssociations,
associateContactToDeal, associateCompanyToDeal,
} from './src/index.js';
// Example: Update a deal stage
await updateDealStage('262356318908', '1103130706'); // Move to "Complete: Won"
// Example: Get full context for a deal
const context = await traverseDealAssociations('262356318908');
console.log(context.deal, context.contacts, context.company);API Reference
Deal Stages (Default Pipeline)
| Stage ID | Label |
|---|---|
appointmentscheduled | To do: Research/Pre-Qualification |
qualifiedtobuy | To do: Circle Back |
presentationscheduled | To do: Lead |
938739088 | In Progress: Qualification Call |
decisionmakerboughtin | In Progress: Discovery Call |
1103130701 | In Progress: Demo |
1103130702 | In Progress: Proposal Creation |
1103130703 | In Progress: Proposal in Review |
1985819336 | In Progress: Verbal Commit |
1103130705 | In Progress: Signing |
1103130706 | Complete: Won |
Available Functions
Companies
| Function | Description |
|---|---|
getCompany(id) | Get company by HubSpot ID |
getCompanyByName(name) | Find company by exact name |
getCompanyByDomain(domain) | Find company by domain |
searchCompanies(query, limit) | Search companies |
Contacts
| Function | Description |
|---|---|
getContact(id) | Get contact by HubSpot ID |
getContactByEmail(email) | Find contact by email |
getContactByName(first, last) | Find contacts by name |
searchContacts(query, limit) | Search contacts |
Deals
| Function | Description |
|---|---|
getDeal(id) | Get deal by HubSpot ID |
createDeal(properties) | Create a new deal |
updateDeal(id, properties) | Update deal properties |
updateDealStage(id, stage) | Change deal stage |
searchDeals(query, limit) | Search deals |
addNoteToDeal(id, note) | Add a note to a deal |
Associations
| Function | Description |
|---|---|
getDealAssociations(dealId) | Get contacts and company for a deal |
getDealWithAssociations(dealId) | Get deal with all associations |
traverseDealAssociations(dealId) | Full context tree traversal |
associateContactToDeal(dealId, contactId) | Link contact to deal |
associateCompanyToDeal(dealId, companyId) | Link company to deal |
Troubleshooting
”HUBSPOT_ACCESS_TOKEN environment variable is required"
# Make sure .env exists and has the token
cat api-service/.env
# If missing, recreate it:
echo "HUBSPOT_ACCESS_TOKEN=$(op read 'op://Personal/Hubspot Brainforge Development App/credential')" > api-service/.env"1Password CLI not signed in"
op signin"MCP not responding in Cursor”
- Restart Cursor completely
- Check MCP status in Cursor settings
- Re-authorize if prompted
- If you only need to inspect data, use the HubSpot app instead of blocking on MCP
”Rate limit exceeded”
The API service has built-in rate limiting (9 req/sec). If you hit limits:
- Wait 10 seconds and retry
- The client auto-retries on 429 errors (up to 3 times)
“Company/Contact not found”
Search functions require exact matches. Try:
- Using
searchCompanies()orsearchContacts()with partial names - Checking for typos in names/emails
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Cursor IDE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ HubSpot MCP │ │ HubSpot API Service │ │
│ │ (Read Only) │ │ (Read/Write) │ │
│ │ │ │ │ │
│ │ - Natural language │ │ - CRUD operations │ │
│ │ - Query deals │ │ - Update deal stages │ │
│ │ - Search contacts │ │ - Add notes │ │
│ │ - Get properties │ │ - Create associations │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ │ │ │
└───────────┼────────────────────────────┼─────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ HubSpot CRM API │
│ │
│ Contacts | Companies | Deals | Notes │
└─────────────────────────────────────────┘
Files Overview
integrations/hubspot/
├── api-service/
│ ├── src/
│ │ ├── index.ts # Main exports
│ │ ├── client.ts # HubSpot client factory
│ │ ├── companies.ts # Company operations
│ │ ├── contacts.ts # Contact operations
│ │ ├── deals.ts # Deal operations
│ │ └── associations.ts # Association traversal
│ └── .env # Your local credentials
├── enrichment/ # Linear ticket enrichment
├── TEAM_SETUP.md # This file
├── CREDENTIALS.md # Credential management details
├── README.md # Overview and architecture
└── QUICK_START_CURSOR.md # MCP quick start
Support
- 1Password Issues: Contact team admin for vault access
- HubSpot Access: Request access through HubSpot admin
- API Questions: Check this guide or ask in engineering Slack
Changelog
| Date | Change |
|---|---|
| 2026-04-01 | Reframed access guidance: HubSpot app first, MCP fallback, API for automation |
| 2026-01-31 | Fixed API: filterGroups for search, v4 associations API |
| 2026-01-16 | Initial integration |