Skip to main content
Glama
bsmahi

LQABR HubSpot MCP Server

by bsmahi

LQABR_MCP

LQABR 중앙 HubSpot MCP 서버입니다. FastMCP 를 기반으로 구축되었으며, 독립형입니다 — LQABR 모노레포에 대한 의존성이 없습니다.

RUNNING.md — 실행 방법: 설정, 자격 증명, 두 가지 전송 방식, 에이전트 게이트웨이, 문제 해결. 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

포크(fork)이지, 이동(move)이 아닙니다

hubspot_mcp/는 모노레포 구현의 복사본입니다. 가져오기(import) 문만 다시 작성되었습니다. 로직, 필드 이름 또는 HubSpot 속성 이름은 변경되지 않았습니다. diff를 통해 확인되었습니다 — 9개 파일 전체에서 유일하게 다른 줄은 다음과 같습니다:

- 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.*

lead_profile 내 12개 파일(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에 추가하지 마십시오.

라이브러리: 공식 SDK가 아닌 FastMCP

의존성은 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로 고정되어 있기 때문에, 번들된 타입들은 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는 pytest 없이 CLI로 실행되지만, uv run pytest는 분명히 pytest가 필요합니다.

.env 파일에는 비밀 값이 없습니다 — HUBSPOT_AUTH_MODE, LQABR_SECRET_PROJECT 및 비밀의 ID만 있습니다. 토큰 자체는 런타임에 Secret Manager API를 통해 가져와 메모리에 유지되며, 절대 로깅되지 않습니다(감사 행에는 길이와 마지막 네 글자만 기록됨). 900초 동안 캐시되므로 회전에 재배포가 필요하지 않습니다.

auth.py와 secrets.py는 모두 실패 시 폐쇄(fail closed)됩니다 — 설정되지 않으면 명시적 오류가 발생하며, 암묵적 기본값이 없습니다.

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가 해결되도록 합니다 — 폴더 자체는 하이픈 때문에 패키지가 될 수 없습니다.

계약은 10개 필드, 9개가 아닙니다

LeadProfile는 10개 필드를 가집니다: 모두가 문서화하는 9개에 contact_name(이름/성 매핑을 위해 추가됨)이 더해집니다. 모노레포의 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 토큰 대상(audience)은 서비스 기본 URL에서 /mcp를 제외한 것입니다. 스크립트가 자동으로 제거합니다.

클라이언트는 fastmcp.Client를 사용하며, 이 클라이언트가 초기화 핸드셰이크를 처리하므로 이 파일에는 세션 배관이 없습니다. 헤더는 StreamableHttpTransport에 실려 있습니다. 이제 참조 샘플의 라이브러리와 일치합니다.

Cloud Run에 배포

2단계 uv 빌드, 비루트 mcp 사용자, PID 1은 Python이므로 SIGTERM이 깔끔하게 드레인됩니다. 자체 포함: 형제 폴더나 git 의존성이 없습니다.

docker build -t lqabr-mcp-server .

uv.lock을 커밋하고, 프로덕션 전에 동기화를 --frozen으로 전환하십시오.

그런 다음 S5–S8: mcp-server-sa 생성, lqabr-hubspot-access-token에 secretmanager.secretAccessor 권한 부여, --no-allow-unauthenticated로 배포, 세 개의 에이전트 서비스 계정에 roles/run.invoker 부여, test_server.py를 해당 서버로 지정하십시오.

노출된 도구

도구

방향

참고사항

upsert_lead_profile

쓰기

회사 upsert → 연락처 upsert → 연결. 멱등성(idempotent).

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을 건드린 것은 없으며, 모든 테스트는 가짜(fake)를 사용합니다.

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.
    -