mcp-crm

mcp-crm

Enables AI agents to safely operate HubSpot CRM contacts, deals, and pipelines via MCP, with caching, idempotency, audit trails, and robust error handling.

Category
访问服务器

README

mcp-crm

CI License: MIT

A HubSpot MCP server (Model Context Protocol) that behaves like production software instead of a thin REST wrapper. It works with a HubSpot free-tier private-app token or a full OAuth app refresh flow, prompts for the exact missing scope instead of leaking a raw 403, retries rate limits with backoff, serves reads from a local cache (TTL plus write-invalidation, with a signature-verified webhook handler you can wire to your own HTTP ingress for out-of-band changes), walks cursor pagination, returns per-item results on batch partial failures, makes writes idempotent, and writes a PII-redacted audit trail of every tool call. Point an MCP client (Claude Desktop, or any MCP host) at it and let an agent operate contacts, deals, and pipelines safely.

See docs/COMPARISON.md for side-by-side transcripts of a naive API-wrapper MCP versus this one — every transcript is generated by running both against the mocked test suite.

Architecture

flowchart TD
    Agent["MCP client / agent"] -->|"stdio (JSON-RPC)"| Server["FastMCP server<br/>server.py"]
    Server --> Service["CrmService<br/>scope checks · audit · orchestration"]

    Service --> Cache["LocalCache<br/>TTL + write invalidation"]
    Service --> Idem["Idempotency store"]
    Service --> Audit["Audit log<br/>PII redaction"]
    Service --> Client["HubSpotClient<br/>retries · pagination · error mapping"]

    Client --> Auth["Token provider<br/>private-app · OAuth refresh"]
    Client -->|HTTPS| HubSpot["HubSpot CRM API"]

    Ingress["Your HTTP ingress<br/>(optional, host-provided)"] -->|"signed v3 payload"| Processor["WebhookProcessor<br/>verify_signature"]
    Processor -->|"invalidate(object)"| Cache

The stdio server speaks JSON-RPC only; it does not listen for webhooks. WebhookProcessor and verify_signature are shipped as a tested component you mount on your own HTTP ingress (see Webhook cache invalidation).

Tools

Tool Purpose Scope
crm_search_contacts / crm_list_contacts search or page contacts crm.objects.contacts.read
crm_get_contact fetch one contact (cached) crm.objects.contacts.read
crm_create_contact / crm_update_contact / crm_delete_contact idempotent write / update / archive crm.objects.contacts.write
crm_batch_create_contacts batch create with partial-failure reporting crm.objects.contacts.write
crm_search_deals / crm_get_deal search / fetch deals crm.objects.deals.read
crm_create_deal / crm_update_deal / crm_delete_deal deal writes crm.objects.deals.write
crm_list_pipelines / crm_get_pipeline pipelines and ordered stages (cached) crm.schemas.deals.read
crm_export_audit_log export the session audit trail as JSONL

Quickstart

Requires Python 3.12+ and uv.

uv venv
uv pip install -e ".[dev]"

cp .env.example .env   # then fill in your HubSpot credentials

Authenticate with either a private-app token (HUBSPOT_PRIVATE_APP_TOKEN) or an OAuth app (HUBSPOT_CLIENT_ID + HUBSPOT_CLIENT_SECRET + HUBSPOT_REFRESH_TOKEN). The server auto-detects which is present.

Run it over stdio:

uv run mcp-crm

Wiring into an MCP client

Add to your MCP host config (for example Claude Desktop's claude_desktop_config.json):

{
  "mcpServers": {
    "hubspot": {
      "command": "uv",
      "args": ["run", "mcp-crm"],
      "env": { "HUBSPOT_PRIVATE_APP_TOKEN": "pat-na1-..." }
    }
  }
}

OAuth authorization URL

For the OAuth flow, mcp_crm.auth.build_authorization_url(...) builds the consent URL with the scopes the tools need; exchange the returned code for a refresh token and set HUBSPOT_REFRESH_TOKEN.

Webhook cache invalidation

The stdio server does not receive webhooks. To invalidate the cache on changes made outside this process (edits in the HubSpot UI, other integrations), mount WebhookProcessor on your own HTTP endpoint and verify HubSpot's v3 signature with verify_signature (using the HUBSPOT_WEBHOOK_SECRET you configure). Point it at the same LocalCache your CrmService uses:

from mcp_crm.webhooks import WebhookProcessor, verify_signature

processor = WebhookProcessor(cache)

def handle_hubspot_webhook(request):
    ok = verify_signature(
        secret=webhook_signing_secret,
        method="POST",
        uri=request.url,
        body=request.raw_body,
        signature=request.headers["X-HubSpot-Signature-v3"],
        timestamp=request.headers["X-HubSpot-Request-Timestamp"],
    )
    if not ok:
        return 401
    processor.process(request.json())
    return 200

verify_signature rejects tampered bodies, wrong secrets, and stale timestamps; WebhookProcessor.process maps each subscription to the object it invalidates and reports what it touched.

Design decisions

  • Cache scope. Only object-detail reads (crm_get_contact, crm_get_deal) and the pipeline list are cached; list/search results are query-dependent and left uncached to avoid serving stale result sets. Writes invalidate the relevant object immediately. For out-of-band changes, a signature-verified WebhookProcessor ships as a component you mount on your own HTTP ingress (see Webhook cache invalidation) — the stdio server itself does not listen for webhooks.
  • Idempotency is client-side. HubSpot's create endpoints are not natively idempotent, so a key (supplied or derived from the payload) is stored and replayed. This makes at-least-once tool retries safe without duplicating records.
  • Scope prompting happens twice. The service pre-checks granted scopes (via token introspection) for a fast, actionable error, and the HTTP client also maps a server-side MISSING_SCOPES 403 to the same typed error — belt and suspenders.
  • Backoff and clocks are injectable. Retry sleep, RNG jitter, and time sources are constructor parameters, which is why the whole suite runs offline in well under a second.

Testing

Every external HubSpot call is served by an in-memory fake (tests/fake_hubspot.py) backed by JSON fixtures (tests/fixtures/), wired in through httpx.MockTransport. No network, no credentials, deterministic.

uv run pytest -q

Regenerate the comparison document (CI also checks it stays in sync):

uv run python scripts/generate_comparison.py

Scope and safety

This is a client for HubSpot data you own or are authorized to access. Point it only at HubSpot accounts you control or have written permission to operate. The audit log redacts emails and phone numbers before writing records; treat exported audit logs as sensitive regardless. Behaviour documented here is point-in-time against the included fixtures, not a guarantee about any live HubSpot account.

Hire me

I build MCP servers and API integrations that survive a senior-dev code review — auth, retries, idempotency, and audit trails included, not bolted on later. Portfolio and contact: https://amin-ale.github.io/portfolio-site · amin.ale.business@gmail.com.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选