LQABR MCP HubSpot Server
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 属性名。
经差异验证——所有九个文件中唯一不同的行是:
- 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 ...单体仓库仍需保留其副本。请勿删除。
模块 | 同时被以下模块使用 |
| lead_profile/src 中的 8 个文件,text_voice/src 中的 2 个文件, |
| 12 个 lead_profile 文件(含 |
| lead_profile |
后果: 现在存在两条写入 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——两者互斥:
fastmcp3.4.7 传递性地锁定mcp<2.0,>=1.24.0(它会安装 mcp 1.29)。MCPServer,即官方 SDK 的服务器类,仅存在于mcp>=2.0中。
因此,一个项目只能使用 FastMCP 或 MCPServer,不能同时使用。本项目使用 FastMCP。此处未导入 MCPServer。
有两个值得注意的后果:
传输名称不同。 FastMCP 的 HTTP 传输名为
"http"("streamable-http"作为别名被接受),端点路径关键字参数为path=。官方 SDK 则使用streamable_http_path=。类型字段不同。 由于 mcp 被锁定在 1.x 版本,捆绑的类型使用 camelCase:
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 HubSpotpytest.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 → 联系人 upsert → 关联。幂等。 |
| 读取 | 契约字段 + |
去重:联系人依据 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— 针对运行中的服务器通过,退出码 0test_server.py在 pytest 下 — 有服务器时 3 个通过,无服务器时 3 个跳过实时
call_tool到达了真实链路:传输 → 工具 →crm.py→auth.py→secrets.py,仅在故意的AuthConfigError/SecretConfigError守卫处失败(测试环境中无 GCP 配置)。端到端布线已验证。
未验证: Docker 镜像从未构建——组装这些文件时没有可用的 Docker 守护进程。未接触过真实的 HubSpot;所有测试均使用模拟数据。
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityFmaintenanceEnables AI models to interact with HubSpot CRM data and operations through a standardized interface, supporting contact and company management.16127MIT
- AlicenseAqualityBmaintenanceExposes HubSpot CRM data and actions as tools for AI agents, enabling contact lookup, company search, contact creation, and activity logging via natural language.4182MIT
- AlicenseAqualityBmaintenanceEnables AI agents to safely operate HubSpot CRM contacts, deals, and pipelines via MCP, with caching, idempotency, audit trails, and robust error handling.15MIT
- AlicenseBqualityBmaintenanceEnables AI assistants to interact with a HubSpot CRM account via natural language, starting with read-only lookups and optionally enabling write operations like creating contacts, deals, and notes.11MIT
Related MCP Connectors
LeadConnector / GoHighLevel MCP Pack — wraps the GoHighLevel CRM for AI agents.
Agent-native CRM. 25 tools — contacts, deals, sequences, enrichment waterfall, audit log.
Connect AI to your Attio CRM. Manage contacts, companies, deals, and sales pipelines. Create tasks…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/TNE736/LQABR_MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server