Announcing CIMD support for MCP Client registration
Learn more

Build a HubSpot CRM Agent with Mastra and Scalekit

Saif Ali Shaik
Founding Developer Advocate

TL;DR

A complete TypeScript script wiring Scalekit's HubSpot tools into a Mastra agent — contacts, deals, calls, pipelines — as individual users, no OAuth code required.

  • User connects HubSpot once via OAuth (Scalekit vaults the token)
  • listTools returns ~17 HubSpot tool definitions; a createTool loop wraps each for Mastra; an MCP alternative skips the loop entirely
  • agent.generate(prompt) drives the loop; tool calls hit scalekit.tools.executeTool with the user identifier; Scalekit makes the HubSpot API call as that specific user

You have a Mastra agent. Now you want it to create contacts, log sales calls, and sweep deal pipelines on behalf of your users, without building OAuth, storing tokens, or writing HubSpot API clients. This post shows a complete TypeScript script that wires Scalekit's authenticated HubSpot tools into a Mastra agent, then walks through three workflow demos that show what it can do.

Prerequisites

  • Node.js 18+, TypeScript
  • npm install @mastra/core @ai-sdk/openai @scalekit-sdk/node zod dotenv
  • A Scalekit account with a HubSpot connection configured (HubSpot connector setup)
  • A HubSpot account for testing
  • An OpenAI API key (or swap the model provider. Mastra supports any AI SDK-compatible model)

How It Fits Together

Your code calls Scalekit; Scalekit handles HubSpot OAuth, token storage, and refresh on behalf of each user.

The Agent Script

The full TypeScript. Copy it, set your environment variables, and run it. The three workflow demos in the next section show what this agent can actually do once it is running.

/** * Mastra agent with Scalekit-authenticated HubSpot tools. */ import { Agent } from '@mastra/core/agent' import { createTool } from '@mastra/core/tools' import { openai } from '@ai-sdk/openai' import { ScalekitClient } from '@scalekit-sdk/node' import { z } from 'zod' import 'dotenv/config' // --- Configuration ----------------------------------------------------------- const IDENTIFIER = process.env.USER_IDENTIFIER || 'user_123' const CONNECTION = 'hubspot' // --- Initialize Scalekit ----------------------------------------------------- // Never hard-code credentials — they would be exposed in source control. // Pull them from environment variables at runtime. const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET!, ) // --- Step 1: Ensure the user has a connected account ------------------------- const { connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({ connectionName: CONNECTION, identifier: IDENTIFIER, }) if (connectedAccount?.status?.toString() !== '1') { // Status 1 = ACTIVE in the protobuf enum const { link } = await scalekit.actions.getAuthorizationLink({ connectionName: CONNECTION, identifier: IDENTIFIER, }) console.log(`\nAuthorization required. Open:\n\n ${link}\n`) console.log('Re-run after completing the OAuth flow.') process.exit(0) } console.log(`Connected account for ${IDENTIFIER} is active.`) // --- Step 2: Discover HubSpot tools from Scalekit ---------------------------- const toolsResponse = await scalekit.tools.listTools({ filter: { connector: CONNECTION, identifier: IDENTIFIER }, pageSize: 50, }) console.log(`Discovered ${toolsResponse.tools.length} tools`) // --- Step 3: Wrap as Mastra tools -------------------------------------------- const mastraTools: Record<string, ReturnType<typeof createTool>> = {} for (const tool of toolsResponse.tools) { const def = tool.definition as Record<string, any> | undefined if (!def?.name) continue const toolName: string = def.name // Use a permissive Zod schema — Scalekit validates inputs server-side. // For stricter client-side validation, convert def.input_schema to Zod. mastraTools[toolName] = createTool({ id: toolName, description: def.description || toolName, inputSchema: z.object({}).passthrough(), execute: async ({ context }) => { return scalekit.tools.executeTool({ toolName, identifier: IDENTIFIER, params: context as Record<string, unknown>, }) }, }) } console.log(`Created ${Object.keys(mastraTools).length} Mastra tools.`) // --- Step 4: Build and run the agent ----------------------------------------- const agent = new Agent({ name: 'hubspot-crm-agent', instructions: 'You are a helpful HubSpot CRM assistant. Use the available tools to manage contacts, deals, companies, and engagements. Confirm what you did after each action.', model: openai('gpt-4o'), tools: mastraTools, }) const prompt = process.argv[2] || 'Find all deals in the Proposal stage with no activity in the past 14 days' console.log(`\nPrompt: ${prompt}\n`) const result = await agent.generate(prompt) console.log(result.text)

Run it

Create a .env file:

SCALEKIT_ENV_URL=https://your-env.scalekit.cloud SCALEKIT_CLIENT_ID=skc_... SCALEKIT_CLIENT_SECRET=sks_... USER_IDENTIFIER=user_123 OPENAI_API_KEY=sk-...

Then run:

npx tsx agent.ts

Or pass a custom prompt as a CLI argument:

npx tsx agent.ts "Create a contact for Sarah Chen at Acme Corp"

Expected output:

Connected account for user_123 is active. Discovered 17 tools Created 17 Mastra tools. Prompt: Find all deals in the Proposal stage with no activity in the past 14 days Found 3 deals in Proposal stage with no recent activity: Acme Corp ($45,000), Beta Inc ($12,000), Gamma Ltd ($67,500).

If this is the first run for the identifier, you will see an authorization link instead. Open it, complete HubSpot OAuth, then re-run the script.

What Your Agent Can Do

These three workflows show the range of tasks the agent handles. Each starts with a natural language prompt and ends with a concrete CRM action.

Qualify a lead

"Create a contact for Sarah Chen at Acme Corp and add her to the Q3 outreach list"

The agent calls hubspot_contact_create with name and company, then adds her to the specified list. You get back: "Contact Sarah Chen created at Acme Corp. Added to Q3 Outreach list."

To run this from the command line:

npx tsx agent.ts "Create a contact for Sarah Chen at Acme Corp and add her to the Q3 outreach list"

Log a sales interaction

"Log a 20-minute call with Sarah Chen — we discussed pricing and she's ready to evaluate"

The agent calls hubspot_call_log with duration, subject, and the associated contact. You get back: "Call logged: 20 min, subject 'Pricing discussion', linked to Sarah Chen."

npx tsx agent.ts "Log a 20-minute call with Sarah Chen — we discussed pricing and she's ready to evaluate"

Pipeline sweep

"Find all deals in Proposal stage with no activity in 14 days and move them to Stalled"

The agent calls hubspot_deals_search to find matching deals, then hubspot_deal_update on each one. You get back: "Found 3 deals. Updated all to Stalled stage."

This is also the default prompt in the script. Run it with no argument to try it against your own HubSpot connection.

Available HubSpot Tools

Category
Tool
Description
Contacts
hubspot_contact_create
Create a new contact
Contacts
hubspot_contact_update
Update contact properties
Contacts
hubspot_contacts_search
Search contacts by name, email, or property
Contacts
hubspot_contacts_list
List contacts with optional filters
Deals
hubspot_deal_create
Create a new deal
Deals
hubspot_deal_update
Update deal properties or stage
Deals
hubspot_deals_search
Search deals by stage, owner, or activity date
Deals
hubspot_deal_pipelines_list
List available deal pipelines and stages
Engagements
hubspot_call_log
Log a call with duration, subject, and contact
Engagements
hubspot_meeting_log
Log a meeting with attendees and notes
Engagements
hubspot_email_create
Create an email engagement on a contact or deal
Engagements
hubspot_note_log
Add a note to a contact, company, or deal
Tasks
hubspot_task_create
Create a follow-up task
Tasks
hubspot_task_complete
Mark a task as complete
Tasks
hubspot_tasks_search
Search tasks by owner, due date, or status
Companies
hubspot_company_create
Create a new company
Companies
hubspot_companies_search
Search companies by name or domain

Full list in the HubSpot connector docs.

Bonus: The MCP Approach

If you prefer a shorter setup, @mastra/mcp lets Mastra auto-discover tools from the Scalekit MCP server. No createTool loop needed.

Install: npm install @mastra/mcp

import { MCPClient } from '@mastra/mcp' import { Agent } from '@mastra/core/agent' import { openai } from '@ai-sdk/openai' import 'dotenv/config' // Generate this URL per user from your backend using the Scalekit SDK. // Never expose this URL on the client side. const mcpUrl = process.env.SCALEKIT_MCP_URL! const mcp = new MCPClient({ servers: { scalekit: { url: new URL(mcpUrl) }, }, }) const tools = await mcp.getTools() const agent = new Agent({ name: 'hubspot-crm-agent', instructions: 'You are a helpful HubSpot CRM assistant.', model: openai('gpt-4o'), tools, }) const result = await agent.generate('Find all deals in Proposal stage with no activity in 14 days') console.log(result.text) await mcp.disconnect()

Mastra fetches the tool schemas directly from the Scalekit MCP server, so you skip the discovery and wrapping steps entirely. To generate the per-user MCP URL from your backend, see the Scalekit MCP docs.

Why This Already Handles Multiple Users

The USER_IDENTIFIER env var (the IDENTIFIER constant in the script) is what makes this scale from one user to many. In production, swap it with the identifier from your authenticated session. Each user in your system gets their own identifier. When they connect HubSpot through the OAuth flow, Scalekit stores their token under that identifier. Every tool call runs with that specific user's permissions.

The same agent code serves every user without modification. Scalekit stores and refreshes OAuth tokens separately per identifier. Your code never sees a token directly.

For a full walkthrough of the multi-user setup, see Who Holds the Token? Credential Ownership Across Agent Tool-Calling Patterns. Prefer Python? See Post-Call CRM Agent: Granola, HubSpot, Gmail, Slack; no Auth Plumbing.

Explore More

Other connectors: Salesforce, Gmail, Slack, Linear, GitHub, Notion, and more: full connector list

Other frameworks: LangChain, CrewAI, Vercel AI SDK, Google ADK: framework examples

No items found.
Agent Auth Quickstart
On this page
Share this article
Agent Auth Quickstart

Acquire enterprise customers with zero upfront cost

Every feature unlocked. No hidden fees.
Start Free
$0
/ month
1 million Monthly Active Users
100 Monthly Active Organizations
1 SSO connection
1 SCIM connection
10K Connected Accounts
Unlimited Dev & Prod environments