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.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues