Skip to main content
Glama
mcpship-hubspot

mcpship-hubspot

README.md
# mcpship-hubspot

A production-shaped HubSpot MCP connector: three read actions plus one guarded write, with an audit log on every call.

This is the same skeleton you hand to a client. Swap the object types in `src/hubspot.ts` and it becomes the Zendesk or Intercom build.

## What's inside

| Tool | Type | What it does |
|---|---|---|
| `search_contacts` | read | Search contacts by name, email, company; falls back to most-recently-modified |
| `search_deals` | read | Search deals by name; no query returns recent deals |
| `recent_notes` | read | Read notes logged against contacts and deals |
| `create_note` | **write, gated** | Writes a note, optionally attached to a deal. Off unless `HUBSPOT_ALLOW_WRITE=true` |

The write tool is off by default. That is deliberate: it is the first thing a security reviewer will ask about, and "it ships disabled, you enable it explicitly" is a much better answer than "it's on".

## 1. Get a token

Do not test this against a real portal. Create a **developer test account** first — it is free, blank, and disposable:

1. Sign in at [developers.hubspot.com](https://developers.hubspot.com) and create a developer account (free).
2. In that account: **Development → Testing → Test Accounts → Create developer test account**.

Then get a token. Which screen you land on depends on the account's age and tier:

**Service keys** — newer and free-tier accounts. HubSpot retired *legacy private apps* for these. Go to **Development → Keys → Service Keys → Create service key**, name it, and select these scopes:

**Private apps** — older accounts only. **Settings → Integrations → Private Apps → Create private app**, same four scopes:

```
crm.objects.contacts.read
crm.objects.deals.read
crm.objects.notes.read
crm.objects.notes.write
```

Copy the token (`pat-...`). It is shown once and cannot be recovered.

> If the notes scopes are not in your account's scope catalogue, `recent_notes` and `create_note` will return 403. `npm run smoke` tells you in ten seconds, which is ten seconds better than finding out mid-demo.

## 2. Build

```bash
cd mcp-hubspot
npm install
npm run build
```

You should end up with `dist/index.js`.

### Fill the test account with something to read

An empty portal makes the tool look broken. This seeds seven companies, nine contacts, seven deals and twelve notes — a plausible B2B SaaS pipeline with one deal blocked on a compliance letter, one won, one lost:

```bash
export HUBSPOT_ACCESS_TOKEN=pat-na1-...
npm run seed
```

Fictional data, but internally consistent. Only run it against a test account; there is no wipe flag on purpose.

### Verify against the real API

`initialize` succeeding proves the server started, not that the calls work. This exercises every tool and prints what came back:

```bash
export HUBSPOT_ACCESS_TOKEN=pat-na1-...
npm run smoke

# include the write tool
HUBSPOT_ALLOW_WRITE=true npm run smoke
```

Run it before you demo, not after.

## 3. Wire it into Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "hubspot": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/mcp-hubspot/dist/index.js"],
      "env": {
        "HUBSPOT_ACCESS_TOKEN": "pat-na1-your-token-here",
        "HUBSPOT_ALLOW_WRITE": "true"
      }
    }
  }
}
```

`args` must be an absolute path. Then fully quit and reopen Claude Desktop — reloading the window is not enough.

## 4. Verify it actually works

Ask Claude:

> What deals did we touch this week?

It should call `search_deals` and return real records. If you get "I don't have access to your data", the server did not load — go to the troubleshooting below.

Check the audit trail:

```bash
tail -f ~/.mcpship/logs/audit.log
```

Every call appends one JSON line: tool, args, ok, duration.

## Troubleshooting

**Server doesn't appear in Claude at all**
Check the MCP log: `~/Library/Logs/Claude/mcp-server-hubspot.log`. A bad absolute path is the usual cause.

**`HUBSPOT_ACCESS_TOKEN is not set`**
The `env` block is not being read. In Claude Desktop, env vars go inside the server entry — shell exports do not reach it.

**401 / 403**
401 means the token is wrong or revoked. 403 means the token is valid but missing a scope — add it in the Private App settings and copy the new token.

**Server loads, then disconnects immediately**
Something is writing to stdout. Under stdio transport, stdout is the JSON-RPC channel; a single `console.log` corrupts the stream. All logging in this project goes to stderr on purpose. Keep it that way.

## Adapting this for a client delivery

1. Change `CONTACT_PROPERTIES` / `DEAL_PROPERTIES` in `src/hubspot.ts` to the fields they actually care about.
2. Rename the tools to match their vocabulary (`search_tickets`, not `search_deals`).
3. Set `MCP_AUDIT_DIR` to a path inside their infrastructure.
4. Hand over the repo plus the token scoping instructions. No vendor dependency, no subscription.

Five days is enough for this because the second build only changes the middle layer.