Skip to main content
Glama
bsmahi

LQABR HubSpot MCP Server

by bsmahi

LQABR_MCP

LQABR 中心 HubSpot MCP 服务器。基于 FastMCP 构建,独立运行 —— 不依赖 LQABR 单体仓库。

RUNNING.md —— 如何运行:设置、凭据、两种传输方式、agentgateway、故障排除。 CONSUMING.md —— 邮件/语音/调度代理如何调用它:ADK McpToolset、认证、通信契约、工具作用域。 本文件是设计原理说明;上述两个文件是操作流程。

LQABR_MCP/
├── hubspot-crm-mcp-server/
│   ├── __init__.py              (empty, per mcp.odt Step 4)
│   ├── hubspot_crm_server.py    the launcher — mcp.odt Step 5
│   ├── test_server.py           remote smoke test — handoff S8
│   ├── hubspot_mcp/             THE IMPLEMENTATION (vendored)
│   │   ├── __init__.py          provenance + drift warning — read this
│   │   ├── server.py            the FastMCP object + the two tools
│   │   ├── secrets.py           Secret Manager access
│   │   ├── obs/                 the four logs: system/process/audit/tokens
│   │   │   ├── __init__.py
│   │   │   ├── context.py       RunContext, run_id, lead_ref_id
│   │   │   └── loggers.py
│   │   └── hubspot/
│   │       ├── __init__.py
│   │       ├── crm.py           upsert_lead_profiles / get_lead_profile
│   │       ├── auth.py          get_hubspot_token(), short-lived M2M
│   │       ├── schema.py        LeadProfile, PushResult, property mapping
│   │       └── failures.py      failure taxonomy + CircuitBreaker
│   └── tests/                   61 tests ported from the mono-repo
├── .vscode/hubspot_mcp.json     stdio config — mcp.odt Steps 7–8
├── Dockerfile                   Cloud Run image — handoff S4
├── .dockerignore
├── pytest.ini
├── .python-version              3.12, matching the Dockerfile
├── .env.example                 mode switches + secret IDs (no values)
├── pyproject.toml
├── RUNNING.md                   step-by-step runbook — start here
├── CONSUMING.md                 client integration guide for other agents
└── README.md

这是分支,而非迁移

hubspot_mcp/ 是单体仓库实现的 副本。仅重写了导入行;逻辑、字段名或 HubSpot 属性名均未更改。通过 diff 验证 —— 所有九个文件中唯一不同的行是:

- from lqabr_core.obs import get_obs, utc_now_iso
+ from ..obs import get_obs, utc_now_iso
- from lqabr_core.leadgen.secrets import ...
+ from ..secrets import ...

单体仓库仍需保留其副本。请勿删除。

模块

也被用于

lqabr_core.obs

lead_profile/src 中的 8 个文件,text_voice/src 中的 2 个文件,lqabr_core/crm

lqabr_core.leadgen.*

12 个 lead_profile 文件(包括 call_mcp.py),以及 evals/run_eval.py

lqabr_core.leadgen.secrets

lead_profile 中的 model.py,2 个测试文件

后果: 现在存在两条写入 HubSpot 的路径,携带相同的字段名和相同的 HubSpot 属性名 —— 而数据契约规定这些名称 就是 契约。对 crm.py 的任何修复、任何认证变更、任何 HubSpot 属性重命名,都必须手动在两个地方同时应用。没有任何机制强制同步。请为此预留预算,或计划淘汰其中一侧。

Related MCP server: hubspot-mcp-server

为何放在单体仓库之外

LQABR 仓库的根目录下有一个顶层包,字面名称为 mcp,这与 FastMCP 依赖的 mcp SDK 冲突。在仓库内部运行会破坏 FastMCP 自身的导入。

请勿将 LQABR 仓库根目录添加到 PYTHONPATH。

库:FastMCP,而非官方 SDK

依赖项为 fastmcp>=3.4.7。请勿添加 mcp>=2.0 —— 两者互斥:

  • fastmcp 3.4.7 传递性地固定了 mcp<2.0,>=1.24.0(它会安装 mcp 1.29)。

  • 官方 SDK 的服务器类 MCPServer 仅存在于 mcp>=2.0。

因此,一个项目只能使用 FastMCP 或 MCPServer,不能同时使用。本项目使用 FastMCP。此处未导入 MCPServer。

有两个值得注意的后果:

  • 传输名称不同。 FastMCP 的 HTTP 传输称为 "http"("streamable-http" 可作为别名接受),端点路径参数为 path=。官方 SDK 则写作 streamable_http_path=。

  • 类型字段不同。 由于 mcp 被固定为 1.x,捆绑的类型使用驼峰命名:Tool.inputSchema,而非 input_schema。

单体仓库中的 lqabr_core/leadgen/server.py 仍使用 MCPServer。这现在是两个代码库之间的第二个分歧点,叠加在分支之上。

运行

uv sync

# stdio — local ADK MCPToolset, or the VSCode config in .vscode/
uv run python hubspot-crm-mcp-server/hubspot_crm_server.py

# HTTP — what Cloud Run runs
uv run python hubspot-crm-mcp-server/hubspot_crm_server.py \
    --transport http --host 0.0.0.0 --port 8080

凭据

tools/list 无需任何凭据。工具 调用 需要 HubSpot 令牌,该令牌来自 Secret Manager —— 上下文 §7.6 / CLAUDE.md §5:凭据从不硬编码,也从不提交。

cp .env.example .env                    # holds mode switches + secret IDs only
gcloud auth application-default login
uv sync --extra gcp --extra test        # both extras; --extra gcp alone drops pytest
export UV_ENV_FILE=.env

uv sync --extra <x> 仅同步 恰好 该额外集合,因此单独使用 --extra gcp 会卸载 pytest。test_server.py 作为 CLI 运行,无需 pytest,但 uv run pytest 显然需要它。

.env 不包含任何秘密值 —— 仅包含 HUBSPOT_AUTH_MODE、LQABR_SECRET_PROJECT 和秘密的 ID。令牌本身在运行时通过 Secret Manager API 获取,保存在内存中,从不记录(审计行仅记录长度和最后四个字符),并缓存 900 秒,因此轮换无需重新部署。

auth.py 和 secrets.py 均采用失败关闭策略 —— 未设置意味着显式错误,而非静默默认值。

LQABR_SECRET_BACKEND=env 作为离线工作或 CI 的最后手段存在。secrets.py 将其限定为“仅限本地开发、CI 和测试”,并且不会自动回退到它 —— 你必须手动输入。它会将实时凭据写入磁盘上的文件。切勿在 Cloud Run 中设置。

每个标志都有环境变量默认值(MCP_TRANSPORT、MCP_HOST、PORT、MCP_PATH),因此容器启动时无需参数 —— Cloud Run 会注入 PORT。

测试

uv run pytest              # 61 tests, all passing, none touch real HubSpot

pytest.ini 将 hubspot-crm-mcp-server/ 添加到路径中,以便 import hubspot_mcp 能够解析 —— 该文件夹本身不能作为包,因为包含连字符。

契约是十个字段,而非九个

LeadProfile 包含十个字段:大家记录的九个,加上 contact_name(为 firstname/lastname 映射而添加)。单体仓库中的 test_wrapper_shape_is_the_nine_fields_plus_ids 仍断言为 9,并且在那里一直失败 —— schema.py 是字节相同的,因此本项目继承了它。

已于 2026-08-18 解决:代码是正确的,数字是过时的。测试已重命名为 test_wrapper_shape_is_the_contract_fields_plus_ids,现在断言字段 名称 而非计数,因此下一次添加会以可读的方式失败。

同样的修复仍需在单体仓库中进行 —— 那里的断言未更改,仍然为红色。声称“9 个字段”的文档也应更正。

测试已部署的服务器

# local
uv run python hubspot-crm-mcp-server/test_server.py

# Cloud Run — mints a Google ID token via ADC
uv run python hubspot-crm-mcp-server/test_server.py \
    --url https://lqabr-mcp-server-xxxx.a.run.app/mcp --auth google

# one real read against HubSpot — writes nothing
... --auth google --employee-id EMP-00042

设计为只读:它从不调用 upsert_lead_profile。发送 X-LQABR-Run-Id,以便服务器的审计日志归因于该调用(B10 修复)。

ID 令牌受众是服务基础 URL 不含 /mcp。脚本会为你剥离它。

客户端使用 fastmcp.Client,它处理初始化握手,因此此文件中没有会话管道。头部信息搭载在 StreamableHttpTransport 上。现在这与参考示例的库一致。

部署到 Cloud Run

两阶段 uv 构建,非 root 用户 mcp,PID 1 为 Python,因此 SIGTERM 能干净地排空。自包含:无同级文件夹,无 git 依赖。

docker build -t lqabr-mcp-server .

提交 uv.lock 并在生产前将同步切换为 --frozen。

然后执行 S5–S8:创建 mcp-server-sa,授予 secretmanager.secretAccessor 权限于 lqabr-hubspot-access-token,部署 --no-allow-unauthenticated,授予三个代理服务账号 roles/run.invoker,将 test_server.py 指向它。

暴露的工具

工具

方向

备注

upsert_lead_profile

写入

公司 upsert → 联系人 upsert → 关联。幂等。

get_lead_profile

读取

契约字段 + contact_hs_id + company_hs_id。

去重:联系人基于 employee_id,公司基于 company_id。电子邮件位于自定义属性 email_id 中。

待办事项。 注册的工具名称为 upsert_lead_profile(单数),但设计文档、会话交接和项目说明均使用 upsert_lead_profiles(复数),且底层函数也是复数。这是通信契约 —— 在任何客户端连接之前解决它。

凭据

两种,从不混淆:

  • Google ID 令牌 证明 代理 → 此服务器(Cloud Run 服务间通信)。

  • HubSpot M2M 令牌 证明 此服务器 → HubSpot,在服务器内部每次调用时生成。调用者永远看不到它。

此服务是 HubSpot 凭据的唯一持有者。

已验证

在一个干净的虚拟环境中,仅包含 fastmcp 3.4.7(它带来了 mcp 1.29)、requests 和 pytest —— 缺少 lqabr_core(确认 ModuleNotFoundError)和 MCPServer(确认 ImportError):

  • 项目中任何地方均无 lqabr_core 导入

  • 移植的测试套件:61 通过,0 失败

  • stdio —— tools/list 返回两个工具,PYTHONPATH 完全剥离

  • --transport http —— 绑定主机/端口/路径,完整的 fastmcp.Client 会话

  • --transport streamable-http —— 别名被接受,提供相同端点

  • test_server.py —— 针对运行中的服务器 PASS,退出码 0

  • test_server.py 在 pytest 下 —— 有服务器时 3 通过,无服务器时 3 跳过

  • 实时 call_tool 到达了真实链路:传输 → 工具 → crm.py → auth.py → secrets.py,仅在故意的 AuthConfigError / SecretConfigError 守卫处失败(测试环境中无 GCP 配置)。端到端接线已验证。

未验证: Docker 镜像从未构建 —— 组装这些文件时没有可用的 Docker 守护进程。未接触过真实的 HubSpot;所有测试均使用模拟。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables comprehensive HubSpot CRM integration through the Model Context Protocol with 15+ tools for managing contacts, companies, and deals. Supports multiple transport protocols (HTTP, SSE, STDIO) with session management and real-time access to CRM data.
    3
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Implements Universal Commerce Protocol (UCP) primitives backed by HubSpot CRM, enabling buyer profile, product catalog, cart, and order operations via MCP tools.
    -