Skip to main content

Production-grade, read-only MCP server for HubSpot CRM

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hubspot_mcp-0.1.0.tar.gz (1.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hubspot_mcp-0.1.0-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

Details for the file hubspot_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: hubspot_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for hubspot_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c1a85dcc71b6fc1fdc0307ef05f037c8882a6743e170cba02822ebd5975e0741
MD5 502e5e2c174cbdb947eb5e62062d65bf
BLAKE2b-256 6ed76d7fff45fe10e2f9d7b6d9e42384bd1f33a86fc706dceb4ac4c27209bba8

See more details on using hashes here.

File details

Details for the file hubspot_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: hubspot_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.7 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for hubspot_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 26d0780c15685586f46f5cb909af69d2b2be637e08623ed4f39604f454b63c99
MD5 721986f58a90ae41b89fbfa1f3a2d413
BLAKE2b-256 3a2129fe18028b30c8c5eb9b98f70bc7cee53a48e49aab54c50ef4aba1d1ec6e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page