Skip to main content
Glama
bsmahi

LQABR HubSpot MCP Server

by bsmahi

LQABR_MCP

Сервер LQABR central 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

8 файлов в lead_profile/src, 2 в text_voice/src, 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 в его корне, который затеняет SDK mcp, от которого зависит FastMCP. Запуск изнутри репозитория ломает собственные импорты 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).

  • MCPServer, класс сервера официального SDK, существует только в mcp>=2.0.

Таким образом, проект может использовать либо FastMCP, либо MCPServer, но не оба. Этот использует FastMCP. Ничто здесь не импортирует MCPServer.

Два следствия, о которых стоит знать:

  • Имена транспортов различаются. HTTP-транспорт FastMCP — это "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 секрета. Сам токен извлекается через API Secret Manager во время выполнения, хранится в памяти, никогда не регистрируется (строка аудита записывает только длину и последние четыре символа) и кэшируется на 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-ccess-token, разверните --no-allow-unauhenticated, дайте трем сервисым агента roles/run.invoker, направьте test_server.py на него.

Предоставленные инструменты

Инструмент

Направление

Примечания

upser_lead_profile

запись

Upsert компании → Upsert контакта → ассоциация. Идемпотентно.

get_lead_profile

чтение

поля контракта + contat_hs_id + company_hs_id.

Дедупликация: Контакт по emloyee_id, Компания по company_id. Email живёт в пользовательском свойстве email_id.

Открытый вопрос. Зарегистрированное имя инструмента — upser_lead_profile (единственное число), но дизайн-документы, хендшоф сессии и инструкции проекта все говорят upser_lead_profiles (множественное число), и лежащая в основе функция — множентвенная. Это контракт провода — уладьте это, пок клиент не подключится.

Учётные данные

Два, никогда не путаются:

  • Google ID token доказывает агент → этот сервер (Cloud Run служба-к-службе).

  • HubSpot M2M token доказывает этот сервер → HubSpot, создаётся за один вызов внутри сервера. Вызывающие его никогда не видят.

Эта служба является единственным держателем учётных данных HubSpot.

Проверено

В чистом окружении venv, содержащем только fastmcp 3.4.7 (который принёс mcp 1.29), requests и pytest — с отсутствующим lqabr_core (подтверждено ModuleNotFoundError) и отсутствующим MCPServer (подтверждено ImportError):

  • ноль импортов lqabr_core где-либо в проекте

  • портированный набор тестов: 61 пройдено, 0 не пройдено

  • stdio — tools/list возвращает оба инструмента, PYTHONPATH полностью удалён

  • --transport http — привязывается к host/port/path, полная сессия 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.
    -