Skip to main content

hubspot-mcp-server

CI License Python 3.11+

hubspot-mcp-server driving read-only HubSpot CRM tools over MCP

A production-grade, read-only MCP server for HubSpot CRM: list and fetch contacts, companies, deals, and tickets (or any custom object), full-text search, walk the CRM's association graph, and discover an object's property schema — from Claude Desktop, Claude Code, or any MCP client — with HubSpot's rate limits handled properly and no live portal required to run the test suite.

Read-only by construction: the HTTP layer this server is built on (hubspot_mcp.client.HubSpotClient) exposes exactly two verbs — get, and post. The only post calls this server ever makes are to HubSpot's CRM Search API (POST /crm/v3/objects/{object}/search), which HubSpot itself models as a POST purely because the search filter body is too large for a query string — it is a read, not a write. The client hard-allowlists this: post() rejects any path that doesn't end in /search with a ValueError before a request is ever sent. There is no put, patch, or delete method anywhere in the codebase.

The server can read whatever the token's scopes allow. A HubSpot Private App access token's granted scopes are the actual security boundary — this server enforces nothing beyond what the token itself is permitted to see. Grant only the read scopes you need (e.g. crm.objects.contacts.read, not every crm.objects.*.read scope) — see the setup walkthrough below.

hubspot-mcp-server is not affiliated with, endorsed by, or sponsored by HubSpot, Inc.

Quickstart

Requires uv and a HubSpot portal to point at. Don't have one? developers.hubspot.com lets you create a free developer account and a test portal in a couple of minutes — no cost, no credit card.

Install and run

This project will be published to PyPI as the distribution hubspot-mcp; its console script is named hubspot-mcp-server (a different, unrelated package already holds the hubspot-mcp-server PyPI name). Because of that mismatch, once published you must tell uvx which distribution to pull the script from — uvx hubspot-mcp-server alone would fetch the wrong package:

uvx --from hubspot-mcp hubspot-mcp-server

That starts the server over stdio. In practice you'll point an MCP client at it instead of running it directly — for Claude Code:

Note: hubspot-mcp is not yet published to PyPI either, so --from hubspot-mcp below won't resolve. Until it's published, replace it with the from-source form uvx --from git+https://github.com/inogen-ai/hubspot-mcp-server hubspot-mcp-server.

claude mcp add hubspot \
  -e HUBSPOT_MCP_ACCESS_TOKEN=<your-private-app-token> \
  -- uvx --from hubspot-mcp hubspot-mcp-server

For Claude Desktop, add to claude_desktop_config.json:

{
  "mcpServers": {
    "hubspot": {
      "command": "uvx",
      "args": ["--from", "hubspot-mcp", "hubspot-mcp-server"],
      "env": {
        "HUBSPOT_MCP_ACCESS_TOKEN": "<your-private-app-token>"
      }
    }
  }
}

Create a Private App and access token

  1. In your HubSpot portal, click the Settings gear icon (top nav).
  2. In the left sidebar, go to Integrations > Private Apps.
  3. Click Create a private app, give it a name (e.g. "hubspot-mcp-server").
  4. Open the Scopes tab and select the read scopes for what you want this server to access — for example crm.objects.contacts.read, crm.objects.companies.read, crm.objects.deals.read, crm.objects.tickets.read, and crm.schemas.contacts.read (and the matching crm.schemas.<object>.read scopes for whichever objects you use list_properties on). Grant only what you actually need — see the scope warning above.
  5. Click Create app, confirm, then copy the access token shown — HubSpot only displays it once. Store it somewhere safe; it's a secret.

Tools

Tool Parameters Returns
list_records object_type: str, properties: str = "", limit: int | None = None Recent records of object_type. Sensible default properties per standard object (see below); properties (comma-separated) overrides them. id is always first. Says plainly when more records are available than limit returned. limit defaults to HUBSPOT_MCP_ITEM_LIMIT when omitted.
get_record object_type: str, record_id: str, properties: str = "" One full record by numeric id — typically an id from a prior list_records/search_records call.
search_records object_type: str, query: str, limit: int | None = None Full-text search of object_type for query (plain text — a name, email, domain, etc. — never filter syntax) via the CRM Search API. Appends "(showing N of total matches)" when more matches exist than limit returned. limit defaults to HUBSPOT_MCP_ITEM_LIMIT when omitted.
get_associations object_type: str, record_id: str, to_object_type: str The to_object_type records associated with one object_type record (e.g. the contacts on a company) — the CRM relationship graph. Each hit's numeric id composes directly into get_record.
list_properties object_type: str The property schema (name, type, label) defined on object_type — discover valid property names before building a properties list. Capped at a multiple of HUBSPOT_MCP_ITEM_LIMIT, with a note when more exist.

Object model cheat sheet

object_type accepts a standard HubSpot object name — contacts, companies, deals, tickets — or a custom object's name, everywhere it appears above. Record ids are always plain numeric strings (e.g. "12345"), never HubSpot's hs_object_id formatted any other way; get_record rejects anything that isn't purely digits before making a request. search_records' query is plain search text — it goes straight into the search request's JSON query field, never string-interpolated into a filter expression.

Environment variables

All settings are prefixed HUBSPOT_MCP_ and can be set in the environment or a .env file (see .env.example).

Variable Default Purpose
HUBSPOT_MCP_ACCESS_TOKEN (unset, required) Private App access token, sent as Authorization: Bearer. See the setup walkthrough above.
HUBSPOT_MCP_BASE_URL https://api.hubapi.com HubSpot API origin. Override for EU data residency or to point the client at a local test double; must be https (a plain-http localhost override is allowed for testing).
HUBSPOT_MCP_ITEM_LIMIT 25 Default page size for list_records/search_records when their limit parameter is omitted, and the base for list_properties's output cap (item_limit × 10, see the Tools table). Pass a per-call limit to list_records/search_records to override it for that call.
HUBSPOT_MCP_TIMEOUT_SECONDS 30.0 HTTP timeout (seconds) per HubSpot request.

HUBSPOT_MCP_ACCESS_TOKEN unset fails startup immediately with a message naming the env var to fix, rather than failing obscurely on the first tool call.

Rate limits

HubSpot Private Apps are subject to daily and per-second API call limits. This server reads HubSpot's X-HubSpot-RateLimit-* headers off every response and appends a warning to a tool's result once daily usage crosses 90% used (10% or less of the daily quota remaining), so a client sees it coming rather than hitting a hard stop mid-session. A 429 is retried honoring Retry-After (clamped to at most 60s, up to 3 retries, exponential 1→2→4s backoff when no header is sent) — except when HubSpot's error body names the DAILY policy, which is not retried (retrying can't fix a quota that only resets at midnight portal time) and instead returns an actionable message immediately.

Security notes

  • Read-only by construction, not by policy — see the warning at the top of this README. The Private App token's granted scopes, not this server, decide what's actually readable.
  • Rate-limit aware. See "Rate limits" above.
  • Credentials never appear in error messages, logs, or exceptions raised to an MCP client — failures are reduced to plain, credential-free sentences before they leave the client layer.
  • Not affiliated with, endorsed by, or sponsored by HubSpot, Inc.

Development

Requires Python 3.11+ and uv.

uv sync
uv run pytest -q
uv run ruff check .

No live HubSpot portal is needed for the test suite — the CRM API is faked at the httpx.MockTransport boundary, and a committed stdio integration gate drives the real server process against an in-process fake CRM API. See docs/manual-verification.md for the live-portal check a maintainer runs before releases.

Contributing

Contributions welcome — see CONTRIBUTING.md for dev setup and PR expectations, and SECURITY.md for reporting vulnerabilities privately.


Not affiliated with, endorsed by, or sponsored by HubSpot, Inc.

Part of InoGen's open-source portfolio: kilnworks (self-hostable RAG assistant) and the read-only MCP connectors m365, servicenow, salesforce, and hubspot.

Built and maintained by InoGen.

Metadata

Release files for hubspot-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hubspot-mcp 0.1.0
File Size Uploaded Provenance
hubspot_mcp-0.1.0.tar.gz 1.3 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for hubspot-mcp 0.1.0
File Interpreter ABI Platform
hubspot_mcp-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.3 MB

Release files / hubspot_mcp-0.1.0.tar.gz

Download URL hubspot_mcp-0.1.0.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
c1a85dcc71b6fc1fdc0307ef05f037c8882a6743e170cba02822ebd5975e0741
BLAKE2b-256 checksum
How to use checksums
6ed76d7fff45fe10e2f9d7b6d9e42384bd1f33a86fc706dceb4ac4c27209bba8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / hubspot_mcp-0.1.0-py3-none-any.whl

Download URL hubspot_mcp-0.1.0-py3-none-any.whl
Size 23.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
26d0780c15685586f46f5cb909af69d2b2be637e08623ed4f39604f454b63c99
BLAKE2b-256 checksum
How to use checksums
3a2129fe18028b30c8c5eb9b98f70bc7cee53a48e49aab54c50ef4aba1d1ec6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for hubspot-mcp 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page