·

HubSpot MCP With Cursor

Set up HubSpot MCP in Cursor so your AI agent can manage contacts, deals, and tickets right from your editor.

Cursor's advantage for HubSpot MCP is proximity: the CRM query and the code it's informing sit in the same window, the same Agent conversation, sometimes the same turn. You can ask "why did we build the export throttling logic in services/export/rate_limiter.ts the way we did" and then, without switching context, ask "pull the HubSpot tickets that originally drove this requirement" — and get both answers grounded in real sources instead of institutional memory that may or may not still be accurate.


Connecting HubSpot MCP to Cursor Agent Mode

Cursor reads MCP server config from .cursor/mcp.json (project-scoped, shareable) or ~/.cursor/mcp.json (global, personal). Project scope is the right default for a team workflow:

{
  "mcpServers": {
    "hubspot": {
      "command": "npx",
      "args": ["-y", "@hubspot/mcp-server"],
      "env": {
        "HUBSPOT_ACCESS_TOKEN": "${env:HUBSPOT_ACCESS_TOKEN}"
      }
    }
  }
}

Cursor resolves ${env:VAR_NAME} from the environment the Cursor application itself was launched in — on macOS, that's often not the same environment as a terminal shell, since GUI apps launched from Finder or Spotlight don't inherit shell profile exports. If the token resolves to an empty string despite being correctly set in .zshrc, launch Cursor from a terminal instead (cursor . from a shell where the var is exported) or set the variable via launchctl setenv for a persistent GUI-level fix on macOS.

export HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
cursor .

Once open, confirm the connection in Cursor Settings → MCP — it lists each configured server with a green/red status indicator and, when expanded, the tool list with parameter schemas. This settings panel is worth checking after every HubSpot MCP server package update, since a version bump can rename or restructure tool parameters (an update that renamed a filters argument to filterGroups, for instance, would silently break existing saved prompts that reference the old shape by name).

Enable Agent mode (not Ask mode) for any session where you want the model to actually execute HubSpot tool calls rather than just discuss what it would do — Ask mode in Cursor is read-only with respect to tool execution by design, which is a useful default to fall back to when you want to review a query plan before it runs against production CRM data.

Tips
- Launch Cursor from a terminal with the token already exported — GUI-launched app instances on macOS frequently don't see shell-profile environment variables, which is the single most common "why is my token empty" issue with this client.
- Check Settings → MCP after any @hubspot/mcp-server version bump — tool parameter shapes aren't guaranteed stable across versions, and a silent rename breaks saved prompts without an obvious error.
- Use Ask mode to preview a query plan on a sensitive or unfamiliar dataset, then switch to Agent mode to actually execute — it's a cheap way to catch an overly broad filter before it runs.


Linking Customer Tickets to Code Areas and Backlog Items

The workflow that makes Cursor uniquely suited to this module is bridging a CRM record to a specific file, function, or existing backlog item, because the agent has both the codebase and the CRM in the same context.

> Find open tickets mentioning "export timeout" or "export fails".
  For each, check if there's a related file in src/services/export/.
  Summarize which code path is likely responsible based on the error
  description in the ticket, and note the ticket ID for reference.

A grounded response might read:

Ticket #4821 ("CSV export hangs on large account") — content mentions
timeout after ~90 seconds on accounts with 60K+ rows. This matches the
synchronous row-by-row serialization in
`src/services/export/csv_generator.ts:112-140`, which has no batching
or streaming. Likely root cause: full in-memory buffer build before
write, no backpressure on very large datasets.

Ticket #4903 — same symptom, different account, same code path.

That's a materially different starting point for a bug ticket than "customers say export is slow" — it names the file, the line range, and a specific technical hypothesis, sourced from two independent customer reports. From here, generating the actual backlog item is a natural next step:

> Draft a backlog item titled based on this finding. Include: problem
  statement (cite ticket IDs #4821, #4903), affected code location,
  proposed fix approach (streaming CSV generation instead of full
  buffer), and estimated customer impact (search all tickets with
  product_area = "export" from the last 90 days for a total count).

For linking to existing backlog items rather than drafting new ones, if your team's issue tracker MCP server (Jira, Linear, GitHub Issues — covered elsewhere in this course) is also connected in the same Cursor session, you can chain across both:

> Check if there's already a Linear issue matching "export timeout" or
  "CSV export". If yes, add a comment linking these two HubSpot tickets
  as additional customer evidence. If no, draft a new issue.

Running HubSpot MCP alongside a second CRM/PM tool's MCP server in the same session is exactly the kind of composability this course is built around — the agent doesn't need a custom integration between HubSpot and Linear, it just has both APIs available as tools and reasons across them.

Tips
- Ask the agent to name a specific file and line range as its hypothesis, not just a general "this is probably related to X" — a vague pointer doesn't save the next engineer any real time.
- When chaining HubSpot with a second MCP-connected tool (Jira, Linear, GitHub), keep both scoped to read-only until you've verified the linking logic once manually — a wrong auto-link between a support ticket and the wrong backlog item is annoying to unwind later.
- Always carry ticket IDs into the drafted backlog item — the traceability is the whole point of doing this research through an agent instead of from memory.


Generating Integration Code Against Real HubSpot Object Schemas

Beyond research, Cursor with HubSpot MCP is useful for a narrower but very concrete task: writing integration code (a webhook handler, a sync job, a custom property mapper) against your actual HubSpot schema, including custom properties, instead of against generic API docs that don't know about your org's fields.

> Get the full property schema for the "deals" object in this HubSpot
  portal, including custom properties. Then generate a TypeScript
  interface `HubSpotDeal` that matches it, using the correct types
  (string, number, enumeration as a union type) for each property.
{
  "tool": "hubspot-get-schemas",
  "arguments": { "objectType": "deals" }
}

A realistic generated interface, once the agent has the schema, correctly reflects org-specific fields alongside HubSpot defaults:

interface HubSpotDeal {
  dealname: string;
  amount: string; // HubSpot returns numeric properties as strings over the API
  dealstage: string;
  pipeline: string;
  closedate: string | null; // ISO 8601, null if unset
  hubspot_owner_id: string | null;
  // Custom properties specific to this portal:
  churn_risk_score: "low" | "medium" | "high" | null;
  renewal_type: "auto" | "manual" | "not_applicable";
}

The comment about amount being returned as a string is exactly the kind of detail that generic documentation glosses over but that a schema-grounded agent catches immediately, because it's reading the actual property type metadata (HubSpot's CRM API returns most numeric and date properties as strings in the default response shape, which trips up a lot of hand-written integration code that assumes native JSON number types).

For a webhook handler, the same schema-grounding applies to validating incoming payloads:

> Generate an Express webhook handler for HubSpot's deal.propertyChange
  event. Validate the payload shape against the deals object schema
  we just pulled. Only process events where the changed property is
  dealstage or amount — ignore all others.

Tips
- Always pull the live schema before generating integration code — HubSpot's numeric properties serialize as strings over the API, and a hand-typed interface based on assumption instead of the real schema will silently produce runtime type mismatches.
- Regenerate the schema-derived types periodically (e.g., in CI, or before any major integration change) — custom properties get added, renamed, or deprecated by non-engineering teams inside HubSpot without a corresponding PR anywhere in your codebase.
- For webhook handlers, explicitly scope which property changes to act on — HubSpot's webhook subscriptions fire on every property change by default if configured broadly, and an unscoped handler processing every field mutation is a common source of noisy, low-value webhook load.


Known Limitations and Workarounds for HubSpot MCP in Cursor

Environment variable resolution on macOS GUI launch, covered above, is the most common day-one blocker — budget for it explicitly when onboarding a new team member rather than treating it as a one-off fluke.

No persistent query cache across sessions. Every new Cursor Agent conversation starts cold with respect to HubSpot schema knowledge — property names, pipeline IDs, and association type IDs discovered in one session aren't remembered in the next. Mitigate this by documenting your portal's key custom properties and pipeline IDs in a .cursor/rules file or project doc that gets included in context automatically, rather than rediscovering them every session.

<!-- .cursor/rules/hubspot-schema.md -->
## HubSpot Portal Reference (Portal ID: 12345678)

- Deal pipeline "Enterprise" = internal ID `987654321`
- Custom deal property `churn_risk_score`: enum [low, medium, high]
- Custom ticket property `product_area`: enum [billing, export,
  integrations, core, other]
- Ticket priority scope note: use property `hs_ticket_priority`, not
  `priority` (deprecated, still present as unused legacy field on
  older portals)

Rate limits under multi-agent parallelism. Cursor supports running multiple agent sessions or background agents in parallel; if two sessions both query HubSpot concurrently against the same private app token, you can hit the per-app rate limit faster than a single-session workflow would suggest. There's no cross-session coordination on this — it's on you to avoid running two heavy HubSpot research sessions at once against the same token.

Write-tool scope is all-or-nothing per token. Cursor doesn't offer a UI-level toggle to disable specific MCP tools per session; the only reliable control over whether hubspot-create-engagement can execute is the scope granted to the private app token itself. If you want a Cursor session that's guaranteed read-only regardless of what the agent attempts, use a token without crm.objects.notes.write (and other write scopes) rather than trusting a prompt instruction alone.

Tips
- Maintain a .cursor/rules file with your portal's key custom property names and pipeline IDs — it eliminates the "rediscover the schema every session" tax and keeps prompts shorter.
- If running parallel Cursor agents that might both touch HubSpot, either stagger them or provision separate private app tokens so rate limits on one don't starve the other.
- Enforce read-only behavior at the token scope level, not the prompt level, whenever a session's write access genuinely doesn't matter for the task — it's the only control Cursor can't accidentally bypass.


Tips

Tips
- Launch Cursor from a terminal with HUBSPOT_ACCESS_TOKEN exported to sidestep the macOS GUI environment-inheritance issue before it costs you a debugging session.
- Use Cursor's dual-context advantage deliberately — the highest-value prompts in this client are the ones that reference both a CRM record and a specific file or function in the same sentence.
- Keep a living schema-reference file in .cursor/rules for your HubSpot portal; it's the cheapest fix for Cursor's lack of cross-session memory and pays for itself after the second or third session.