Skip to main content
Glama
HubSpot CRM MCP server

HubSpot CRM MCP server

by dpittaluga76
README.md
# HubSpot CRM MCP server

A free, read-only Python [MCP](https://modelcontextprotocol.io) server (stdio) that lets AI agents read your whole HubSpot CRM:
- **Records:** contacts, companies, deals, tickets, products, line items, quotes, leads and custom objects
- **Logged activities:** calls, emails, meetings, notes and tasks
- **Context:** associations, pipelines, owners and property definitions

It works with Claude Code, Claude Desktop, Cursor, VS Code / GitHub Copilot, Codex and any other MCP client.

**Read-only and local:**
- It never creates, changes or deletes CRM records.
- It runs on your machine.
- The only host it contacts is `api.hubapi.com`, using your own HubSpot token.

Licensed under the [MIT License](LICENSE). Not affiliated with or endorsed by HubSpot, Inc.

## Tools

**Generic tools.** These work with any object type: `contacts`, `companies`, `deals`, `tickets`, `products`, `line_items`, `quotes`, `calls`, `emails`, `meetings`, `notes`, `tasks`, `leads`, or a custom object ID such as `2-1234567`.

| Tool | What it does |
|---|---|
| `list_object_types` | Lists standard and custom object types, with the default properties returned for each |
| `list_properties` | Lists an object type's fields. With `search`, also returns descriptions and dropdown options |
| `list_pipelines` | Lists deal or ticket pipelines and their stages, with probability and closed flag |
| `list_owners` | Lists the users who can own records. Use it to map `hubspot_owner_id` to names |
| `list_objects` | Lists records of a type, with optional `properties` and paging |
| `get_object` | Gets one record by ID. With `associations`, also returns the IDs of linked records |
| `search_objects` | Searches with free text, `filters` (all must match), `sorts`, `properties` and paging. Returns `total` |
| `list_associations` | Lists the IDs of linked records of one type, e.g. a company's contacts or a deal's notes, with association labels |

**Shortcuts** for the most common calls: `list_contacts`, `list_companies`, `list_deals`, `get_contact` and `search_contacts`.

**Arguments and results**
- `limit` is 1-100 and defaults to 10 (100 for owners and associations).
- List and search results include `next_after`. Pass it as `after` to get the next page.
- Records come back flat: `{id, ...properties}`.

**Search filters** take this form: `{"property": "amount", "operator": "GTE", "value": "5000"}`. The operators are:
- `EQ`, `NEQ`, `LT`, `LTE`, `GT`, `GTE`
- `BETWEEN`: needs `value` and `high_value`
- `IN`, `NOT_IN`: need `values`
- `HAS_PROPERTY`, `NOT_HAS_PROPERTY`
- `CONTAINS_TOKEN`, `NOT_CONTAINS_TOKEN`

## 1. Install

Requires Python 3.10 or later.

```bash
git clone https://github.com/dpittaluga76/hubspot-crm-mcp
cd hubspot-crm-mcp
python -m venv .venv
# Windows:      .venv\Scripts\python -m pip install -r requirements.txt
# macOS/Linux:  .venv/bin/python -m pip install -r requirements.txt
```

## 2. Try the demo (no HubSpot account needed)

Run these from the cloned folder (examples use Windows paths):

```powershell
.\.venv\Scripts\python.exe client.py --demo
.\.venv\Scripts\python.exe client.py --demo --tool list_pipelines
.\run_hubspot.bat --demo --tool get_object --args '{\"object_type\": \"companies\", \"object_id\": \"101\", \"associations\": [\"contacts\", \"deals\"]}'
```

The client starts the server, prints the available tools, then calls the one you chose (`list_contacts` by default). Demo mode serves linked fictional records for every tool. The demo data is in `demo_data.py`.

## 3. Connect to HubSpot

1. Create a private app (**Settings → Integrations → Private Apps**) or a service key (**Settings → Integrations → Service Keys**). Tick only the **read** scopes for the data you want:

   | Data | Scope |
   |---|---|
   | Contacts, plus calls, meetings, notes and tasks | `crm.objects.contacts.read` |
   | Companies | `crm.objects.companies.read` |
   | Deals, deal pipelines | `crm.objects.deals.read` |
   | Tickets, ticket pipelines | `crm.objects.tickets.read` (older accounts: `tickets`) |
   | Leads | `crm.objects.leads.read` |
   | Products | `crm.objects.products.read` |
   | Line items | `crm.objects.line_items.read` |
   | Quotes | `crm.objects.quotes.read` |
   | Logged emails | `crm.objects.emails.read` |
   | Owner names | `crm.objects.owners.read` |
   | Custom objects | `crm.schemas.custom.read`, `crm.objects.custom.read` |

   Leave every write, import, export and sensitive-data scope unticked: the server never needs them. Some scopes need a paid HubSpot plan. If a scope is missing, the tool's error message (HTTP 403) names the scope HubSpot expects.
2. Copy `.env.example` to `.env` and paste the token into it. The server looks for a credential in this order:
   1. `HUBSPOT_ACCESS_TOKEN` environment variable
   2. `HUBSPOT_ACCESS_TOKEN` in `.env`
   3. `HUBSPOT_ACCESS_TOKEN_BASE64` in `.env`

   Base64 only keeps the token from being readable at a glance. It is not encryption. The server reads `.env` from its own folder and never copies it into the process environment.
3. Run `client.py` without `--demo` to check the connection.

Results contain real CRM data, including personal information, so use a test account if you can.

## 4. Register it with your agent

In the configs below:
- `<python>` is the absolute path of the `python` executable in `.venv`
- `<server>` is the absolute path of `server.py`

Add `--demo` after `<server>` for fictional data. Your token stays in `.env`, so it never goes into agent config.

**Claude Code**
```bash
claude mcp add -s user hubspot -- <python> <server>
```

**Cursor:** `~/.cursor/mcp.json`. **Claude Desktop:** `claude_desktop_config.json`. Both use the same block (see also [.mcp.json.example](.mcp.json.example)):
```json
{"mcpServers": {"hubspot": {"command": "<python>", "args": ["<server>"]}}}
```

**VS Code / GitHub Copilot (agent mode):** `.vscode/mcp.json`
```json
{"servers": {"hubspot": {"type": "stdio", "command": "<python>", "args": ["<server>"]}}}
```

**Codex CLI**
```bash
codex mcp add hubspot -- <python> <server>
```

Restart the agent and ask, for example, "Which deals close this month?" or "Summarize everything we have on Example Corp".

## Tests

```bash
python -m unittest -v
```

The 32 tests make no network calls. They cover:
- demo data for every tool
- the exact HubSpot requests sent: paths, query parameters, search bodies, associations and paging
- credential lookup order
- error messages, including which scope a 403 is missing
- input validation, e.g. object types and IDs can't change the request path
- full client-to-server runs over stdio

## Files

- `server.py`: the MCP server. It uses HubSpot's date-versioned API (`/crm/objects/2026-03/...`, plus `/crm/v3/schemas` for custom objects).
- `demo_data.py`: the fictional records served by `--demo`.
- `client.py`: a minimal stdio MCP client (`--demo`, `--tool`, `--limit`, `--args`).
- `run_hubspot.bat`: Windows launcher for the client. It passes its arguments through.
- `test_server.py`: the offline test suite.
- `.env.example`, `.mcp.json.example`: templates. The real `.env` and `.mcp.json` are git-ignored.