OpenCode's MCP support is intentionally minimal and transparent — a single TOML or JSON config block, no proprietary permission layer on top, and tool calls printed inline in the terminal exactly as the server returns them. That makes it a good client for learning what HubSpot MCP actually sends over the wire, and a slightly rougher one for day-to-day CRM research because you lose some of the guardrails Claude Code and Cursor add by default.
Installing and Connecting HubSpot MCP to OpenCode
OpenCode reads MCP server definitions from opencode.json (project-level) or ~/.config/opencode/opencode.json (global). Add the HubSpot server as a local (stdio) MCP entry:
{
"mcp": {
"hubspot": {
"type": "local",
"command": ["npx", "-y", "@hubspot/mcp-server"],
"environment": {
"HUBSPOT_ACCESS_TOKEN": "{env:HUBSPOT_ACCESS_TOKEN}"
},
"enabled": true
}
}
}
OpenCode's {env:VAR_NAME} interpolation pulls from the shell environment at launch time, so export the token before starting OpenCode, same as any other client:
export HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
opencode
Confirm the connection inside a session with /mcp (or the equivalent server-status command in your installed OpenCode version — this has moved between releases, check opencode --help if /mcp doesn't resolve). A connected server lists its available tool names; if hubspot appears with zero tools, check the process didn't crash on startup by running the command manually first:
HUBSPOT_ACCESS_TOKEN=$HUBSPOT_ACCESS_TOKEN npx -y @hubspot/mcp-server
If that hangs waiting on stdio (expected — it's a stdio server, not an HTTP one) and doesn't print an auth error, the token is being read correctly and the issue is elsewhere in the OpenCode config, most likely a JSON syntax error in opencode.json swallowing the whole mcp block silently.
Unlike Claude Code, OpenCode does not have a first-class distinction between a "shared, committed" server config and a "personal, local" one out of the box — treat opencode.json as safe to commit only after confirming it has no literal token in it (interpolation only), and keep the actual token in your shell profile or a per-machine .env sourced before launch.
Tips
- Test the MCP server binary manually (npx -y @hubspot/mcp-server) before wiring it into OpenCode — it isolates whether a connection failure is the server, the token, or the OpenCode config.
- OpenCode's tool-call output is far less abbreviated than Claude Code's — expect to see full JSON arguments and full JSON responses in the terminal, which is verbose but genuinely useful for learning the API shape.
- Double-checkopencode.jsonfor stray trailing commas after editing by hand — a malformed JSON config fails closed with a generic startup error, not a pointer to the syntax issue.
Reading CRM Objects and Associations from OpenCode
Once connected, querying works the same conceptually as any MCP client — natural language in, tool calls out — but OpenCode's terminal UI shows you the raw request/response pairs by default, which is worth using deliberately while you're still learning your HubSpot instance's schema.
> List the first 20 companies sorted by number of associated open
deals, descending. Show company name, domain, and deal count.
This isn't a single API call HubSpot supports natively — there's no "deal count per company" property out of the box — so watch how the agent decomposes it: typically a hubspot-list-objects call for companies, then a hubspot-list-associations call per company to deals, then a client-side sort. For any account with more than a few hundred companies, that's an N+1 pattern that will be slow and will burn through API rate limits (HubSpot's default private app rate limit is 100 requests per 10 seconds on the Professional tier, higher on Enterprise). Watch for this and redirect the agent toward a filtered search instead when the object count is large:
> Search deals with dealstage = "qualifiedtobuy" or later, limit 100,
properties: dealname, amount, associated company. Then aggregate by
company client-side.
That flips the N+1 around — one search over deals, with the company association embedded via HubSpot's associations parameter on the search call, instead of one lookup per company:
{
"tool": "hubspot-search-objects",
"arguments": {
"objectType": "deals",
"filterGroups": [
{ "filters": [{ "propertyName": "dealstage", "operator": "EQ", "value": "qualifiedtobuy" }] }
],
"properties": ["dealname", "amount"],
"associations": ["companies"],
"limit": 100
}
}
For association walks specifically — contact to company, deal to line items, ticket to contact — OpenCode surfaces the raw association type ID in responses, which is genuinely useful for debugging: HubSpot associations carry both a category (HUBSPOT_DEFINED vs USER_DEFINED) and a numeric type ID, and a custom association label your team created won't resolve correctly if the agent assumes the default type ID.
Tips
- Watch for N+1 association patterns in multi-step queries and redirect toward theassociationsparameter onhubspot-search-objectsdirectly — it fetches the association in the same call instead of one call per object.
- HubSpot private app rate limits are real and OpenCode won't back off gracefully by default — if a session starts returning 429s, pause and let the agent add a delay, or narrow the query instead of retrying blindly.
- When associations involve custom labels (not HubSpot's defaults), ask the agent to callhubspot-get-schemasor list association types explicitly rather than assuming the first association type ID it finds is the right one.
Practical Example: Summarizing Top Customer Complaints from Tickets
Here's a full worked example — the kind of task this module exists to make routine. Assume a SaaS company with a product_area custom property on tickets, populated by support agents at triage time.
> Search tickets created in the last 60 days with hs_ticket_priority
in [HIGH, URGENT]. Properties: subject, content, product_area,
createdate. Limit 100, and tell me if the total exceeds 100.
Group by product_area, and for each group give: count, and a
2-sentence summary of the recurring complaint pattern based on the
ticket content.
OpenCode issues the search, and — since good tickets are noisy and inconsistent — a well-behaved response looks like this:
## High/Urgent Tickets by Product Area (last 60 days)
Total matching: 134 (showing first 100 — recommend re-running with
pagination for full accuracy)
### Billing (41 tickets)
Recurring pattern: customers report being charged after downgrading
their plan mid-cycle; several mention confusion about proration timing
in the account settings UI.
### Data Export (23 tickets)
Recurring pattern: CSV export times out or produces truncated files for
accounts with more than ~50K rows; customers are working around it by
requesting manual exports from support.
### Integrations (19 tickets)
...
Notice the model flagged that total (134) exceeded what it fetched (100) — that only happens if you explicitly ask for it, as in the prompt above. Without that instruction, most agents report "134 tickets" or silently analyze only 100 without saying so, which is the exact failure mode worth training yourself to check for on every summarization task against paginated CRM data.
The "Data Export" theme here is a concrete, quantified signal — 23 tickets, a specific technical symptom (timeout above ~50K rows), and a known workaround — that converts almost directly into an engineering ticket with an acceptance criterion.
Tips
- Always instruct the agent to report actual vs. total counts when the query might be paginated — this single habit prevents the most common wrong-conclusion failure in CRM summarization.
- Ask for a named workaround or symptom threshold (not just "customers complain about exports") — specificity in the summary is what makes it usable as an acceptance criterion later.
- Re-run complaint-clustering prompts with a wider date range occasionally to check whether a theme is a new spike or a long-running background complaint — the two need very different prioritization.
Known Limitations for HubSpot MCP in OpenCode
Three limitations are worth knowing before you build a habit around this combination:
No built-in approval gating on write tools. Unlike Claude Code's per-call approval cards, OpenCode's default permission model is coarser — depending on your configured permission mode, hubspot-create-engagement may execute without an explicit per-call confirmation. If your OpenCode config has permissions set broadly (e.g., "edit": "allow" applied loosely across MCP tools), a write-capable HubSpot tool can fire without the pause you'd get in Claude Code. Explicitly scope permissions for the HubSpot server, or omit write scopes from the private app token entirely so a mistaken write fails at the API level instead of relying on client-side gating.
No native pagination helper. OpenCode won't auto-paginate a hubspot-search-objects call past its limit; every "get me everything" request needs an explicit instruction to loop on the after cursor, or it silently stops at page one. This is a client behavior, not a HubSpot API limitation — the API itself supports cursor pagination fine.
Verbose transcripts get expensive on large result sets. Because OpenCode doesn't summarize or truncate tool responses as aggressively as some other clients, a hubspot-list-objects call returning 100 full contact records with all default properties can consume a meaningful chunk of context window fast. Combine this with the earlier advice to always pass an explicit properties list — it's a token-budget concern here, not just a privacy one.
Rate-limit backoff isn't automatic. As noted above, a burst of association-walk calls can hit HubSpot's per-10-second rate limit; OpenCode will surface the 429 error but won't automatically slow down and retry with backoff — you'll need to ask the agent to pause and retry, or restructure the query to use fewer, larger calls.
Tips
- Explicitly restrict the HubSpot MCP server's permission scope in OpenCode's config rather than relying on a broad default — pair this with a read-only private app token as a second layer of defense.
- For any "fetch everything" task, write the pagination loop instruction into the prompt itself (checktotal, loop onafter, stop when exhausted) rather than assuming the client handles it.
- Keep an eye on context usage during large CRM pulls — if a session starts truncating or forgetting earlier context, it's often the CRM tool responses, not the conversation, eating the budget.
Tips
Tips
- OpenCode's transparency is a feature during onboarding — use its verbose tool-call output specifically to learn HubSpot's object model and your account's custom properties before moving day-to-day work to a client with more automation.
- Keep write scopes off the private app token used with OpenCode until you've configured explicit permission gating — the API-level restriction is cheaper insurance than relying on client behavior.
- Bookmark the property names and pipeline/stage IDs you discover in early OpenCode sessions — there's no persistent schema cache across sessions, so you'll rediscover them from scratch each time otherwise.