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이것은 포크(fork)이지, 이동이 아닙니다
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 ...모노레포는 여전히 자신의 복사본이 필요합니다. 삭제하지 마십시오.
모듈 | 다른 사용처 |
| lead_profile/src의 8개 파일, text_voice/src의 2개 파일, |
|
|
| lead_profile |
결과: 이제 동일한 필드 이름과 동일한 HubSpot 속성 이름을 사용하는 두 개의 HubSpot 쓰기 경로가 존재하며, 데이터 계약에 따르면 해당 이름이 계약 그 자체입니다. crm.py의 수정, 인증 변경, HubSpot 속성 이름 변경은 모두 수동으로 두 곳에 적용되어야 합니다. 이를 강제하는 것은 없습니다. 이에 대한 비용을 예산에 반영하거나, 한쪽을 폐지할 계획을 세우십시오.
Related MCP server: LQABR HubSpot MCP Server
왜 이 모노레포 외부에 있는가
LQABR 저장소는 루트에 문자 그대로 mcp라는 이름의 최상위 패키지를 가지고 있으며, 이는 FastMCP가 의존하는 mcp SDK를 가립니다. 저장소 내부에서 실행하면 FastMCP 자체의 import가 깨집니다.
LQABR 저장소 루트를 PYTHONPATH에 추가하지 마십시오.
라이브러리: 공식 SDK가 아닌 FastMCP
의존성은 fastmcp>=3.4.7입니다. mcp>=2.0을 추가하지 마십시오 — 두 라이브러리는 상호 배타적입니다:
fastmcp3.4.7은 전이적으로mcp<2.0,>=1.24.0을 고정합니다 (mcp 1.29를 설치함).공식 SDK의 서버 클래스인
MCPServer는mcp>=2.0에만 존재합니다.
따라서 프로젝트는 FastMCP 또는 MCPServer 중 하나만 사용할 수 있으며, 둘 다 사용할 수 없습니다. 이 프로젝트는 FastMCP를 사용합니다. 여기서 MCPServer를 import하는 것은 없습니다.
알아두어야 할 두 가지 결과:
전송 이름이 다릅니다. 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는 분명히 필요합니다.
.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 HubSpotpytest.ini는 hubspot-crm-mcp-server/를 경로에 추가하여 import hubspot_mcp가 해결되도록 합니다 — 폴더 자체는 하이픈 때문에 패키지가 될 수 없습니다.
계약은 9개가 아닌 10개 필드입니다
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 → 연락처 upsert → 연결. 멱등성(idempotent). |
| 읽기 | 계약 필드 + |
중복 제거: 연락처는 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_coreimport 없음포팅된 테스트 스위트: 61개 통과, 0개 실패
stdio —
tools/list가 두 도구를 반환,PYTHONPATH완전히 제거됨--transport http— 호스트/포트/경로 바인딩, 전체fastmcp.Client세션--transport streamable-http— 별칭 허용됨, 동일한 엔드포인트 제공test_server.py— 실행 중인 서버에 대해 PASS, 종료 코드 0test_server.pyunder pytest — 서버와 함께 3개 통과, 서버 없이 3개 건너뜀실제
call_tool이 실제 체인에 도달함: 전송 → 도구 →crm.py→auth.py→secrets.py, 의도적인AuthConfigError/SecretConfigError가드에서만 실패 (테스트 환경에 GCP 구성 없음). 배선이 종단 간 입증됨.
검증되지 않음: Docker 이미지는 한 번도 빌드되지 않았습니다 — 이 파일들이 조합된 곳에서는 Docker 데몬을 사용할 수 없었습니다. 실제 HubSpot을 건드린 것은 없으며, 모든 테스트는 가짜(fake)를 사용합니다.
Available Tools
4 toolsget_blog_summaryA
Read one blog summary row by its publication timestamp. Read-only. Returns the seven contract fields plus ticket_hs_id. Not-found is a VALID result — found is false and summary is null — never an error. Check warnings: a non-empty list means more than one row shares this timestamp and the first was returned.
| Name | Required | Description | Default |
|---|---|---|---|
| blog_published_at | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description takes full responsibility for behavioral transparency. It clearly states the tool is read-only, describes the return fields and the non-error 'not-found' behavior, and warns about potential ambiguity with shared timestamps. The only minor omission is not specifying the exact return structure or whether it includes pagination.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is four sentences, each with distinct value: purpose, safety, error handling, and edge-case warning. It is front-loaded with the core action, no wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simple single-parameter tool and presence of an output schema (which handles return structure), the description covers purpose, behavioral edges, and warnings adequately. The omission of timestamp format detail is minor and acceptable for a tool with just one parameter and no nested objects.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explains that the single parameter 'blog_published_at' is a publication timestamp for identifying the row, which aligns with the schema. However, it does not provide format details (e.g., ISO 8601) or validation constraints beyond that, leaving the agent to infer from context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description specifies a clear verb ('read'), a specific resource ('blog summary row'), and the unique identifier ('publication timestamp'). It also distinguishes from siblings like 'upsert_blog_summary' and 'get_lead_profile' by focusing on reading a blog summary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides context on when to use this tool: reading a single blog summary by timestamp. It does not explicitly mention when not to use it or alternatives, but the sibling list and the read-only nature imply a contrast with upsert_blog_summary. Some guidance on the 'not-found' and 'warnings' behavior aids correct usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_lead_profileA
Read one lead's current state from HubSpot. Read-only. Returns the nine contract fields plus contact_hs_id and company_hs_id so you can write status back without re-searching. Check company_resolved: when false, the company fields are unpopulated and warnings explains why.
| Name | Required | Description | Default |
|---|---|---|---|
| No | |||
| employee_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden for behavioral disclosure. It covers read-only safety, enumerates the returned fields, and explains the company_resolved flag and warnings. This is substantial context for a read tool, though it does not cover error cases or edge behaviors in detail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no redundancy. The first sentence states the core purpose and safety, and the second adds essential behavioral details. Every word contributes value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read tool with an output schema, the description covers the main aspects: purpose, return fields, and a key data-quality flag. However, it omits parameter usage, which is a notable gap given the low schema coverage. The output schema likely documents return values, so that is not a deficiency.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It does not mention email or employee_id at all, nor explain how the lead is identified, whether identifiers are mutually exclusive, or what happens if neither is provided. The schema only provides types and defaults, leaving the semantic meaning entirely unexplained.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Read one lead's current state from HubSpot.' It uses a specific verb and resource, and explicitly declares read-only behavior. It differentiates from siblings by focusing on leads rather than blog summaries and by contrasting with upsert operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context by stating the returned IDs are 'so you can write status back without re-searching,' suggesting it is the read-before-write companion to upsert_lead_profile. It also instructs to check company_resolved for interpreting data. However, it does not explicitly name alternatives or provide when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upsert_blog_summaryA
Put one published blog post into HubSpot: the ONLY writer to the blog summary store. Searches on blog_published_at, then updates that row or creates one — a single upsert, so calling it twice for the same post is safe and does not duplicate. Returns the ticket id and whether the row was created or updated. blog_industry must be exactly one of FINANCIAL_SERVICES, LEGAL_SERVICES, HEALTHCARE — a near-miss selects zero leads and raises no error. Bad data is recorded and reported as failed, never silently dropped.
| Name | Required | Description | Default |
|---|---|---|---|
| subject | Yes | ||
| blog_summary | Yes | ||
| blog_industry | Yes | ||
| summary_ref_id | No | ||
| blog_published_at | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and excels: it reveals the upsert logic (no duplicates), mentions the return value (ticket id and created/updated status), warns about exact value requirements for blog_industry, and states that bad data is recorded and reported as failed. This is comprehensive behavioral disclosure beyond what any annotation could provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact, every sentence adds critical information, and key points are front-loaded. It covers purpose, safety, return value, parameter constraints, and error handling without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has 5 parameters, an output schema, and no annotations, the description provides all necessary behavioral context including upsert semantics, exact enum values, and failure behavior. The return value is described, and the output schema structure is implied but not needed in detail. This is complete for an agent to use the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage, so the description must compensate. It explicitly explains the blog_industry parameter's allowed values and failure behavior, and implies the meaning of blog_published_at as the search key. It does not detail subject, blog_summary, or summary_ref_id, but the overall intent is clear enough given the tool's purpose.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool puts a blog post into HubSpot using an upsert strategy, specifies it is the only writer to the blog summary store, and distinguishes itself from siblings by focusing on blog summaries rather than lead profiles. The verb 'put' combined with 'published blog post' and 'upsert' gives a concrete, specific purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states this is for published blog posts and mentions that calling it twice is safe due to upsert behavior. It does not explicitly list when not to use it or name alternatives among siblings, but the sibling tools (upsert_lead_profile, get_lead_profile, get_blog_summary) have different focuses, so the usage context is clear enough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upsert_lead_profileA
Create or update one lead in HubSpot: upserts the Company, upserts the Contact, and associates them. Creating and updating are ONE upsert, so calling this twice for the same lead is safe and does not duplicate. Returns the HubSpot ids. Bad data is recorded and reported as failed, never silently dropped.
| Name | Required | Description | Default |
|---|---|---|---|
| No | |||
| phone | No | ||
| industry | No | ||
| job_title | No | ||
| company_id | Yes | ||
| employee_id | Yes | ||
| lead_ref_id | No | ||
| annual_revenue_m | No | ||
| decision_maker_flag | Yes | ||
| frequency_of_purchase | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of disclosing behavior. It reveals the internal operations (upsert Company, upsert Contact, associate), idempotency, return value (IDs), and error handling (bad data recorded and reported, never silently dropped). This is comprehensive for safety and side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences, each earning its place: first sentence defines the action, second sentence emphasizes idempotency, third sentence covers error handling and return values. No redundancy or unnecessary detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description adequately covers purpose, safety, and error behavior, and the output schema covers return values. However, given the high parameter count (10) and zero schema descriptions, the lack of parameter guidance leaves the description incomplete for correct invocation. It is sufficient for understanding what the tool does but not for parameter selection.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description does not explain the purpose or format of any of the 10 parameters (e.g., what employee_id, company_id, decision_maker_flag represent). The description must compensate for the lack of schema parameter descriptions, but it fails to do so, leaving the agent to guess parameter semantics from names alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Create or update one lead'), the resource ('lead in HubSpot'), and specifics that it upserts both Company and Contact and associates them. It distinguishes itself from sibling tools like 'get_lead_profile' (read-only) and 'upsert_blog_summary' (different entity).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains that calling the tool twice is safe and does not duplicate, indicating idempotency. However, it does not provide explicit guidance on when to use this tool versus alternatives (e.g., get_lead_profile for reads) or when not to use it, leaving usage context implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
get_blog_summary - First observed
get_lead_profile - First observed
upsert_blog_summary - First observed
upsert_lead_profile
TDQS
Scored across 4 tools
The tools clearly separate lead and blog summary domains, but within each domain the upsert and get tools have high semantic overlap in purpose, though descriptions help distinguish read vs. write.
All tool names follow a consistent verb_noun pattern (upsert/get + lead_profile/blog_summary) with snake_case, very predictable. Minor point: 'upsert' is a valid term but less common than 'create_or_update'.
4 tools is a reasonable count for a focused HubSpot integration covering two entities (lead and blog summary) with basic CRUD. It's slightly thin but appropriate for a narrow scope.
Coverage is incomplete: lead profiles have upsert+get but no ability to delete or list/filter leads. Blog summaries similarly lack delete and listing. The domain surface has notable missing operations that could hinder workflows.
Maintenance
Related MCP Connectors
The HubSpot MCP Server acts as a bridge that enables AI assistants and Large Language Models to securely interact with HubSpot CRM data through natural conversation, without requiring users to understand complex API structures. It provides read-only access to standard CRM objects (contacts, companies, deals, tickets, products, invoices, and more) and their associations, secured via OAuth 2.0, allowing AI agents to perform tasks like summarizing deals, fetching company updates, and looking up record changes.
Give AI agents real hands on LinkedIn: sourcing, AI qualification, HubSpot-native attribution.
34-tool GTM gateway: CRMs, ad platforms, analytics, Google Workspace, AWS, and LLM orchestration.
Run B2B outreach from your AI agent: 250+ tools for campaigns, leads, LinkedIn and email workflows.
Related MCP Servers
- AlicenseAqualityCmaintenanceExposes HubSpot CRM data and actions as tools for AI agents, enabling contact lookup, company search, contact creation, and activity logging via natural language.4890 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables interaction with HubSpot CRM through two tools (upsert_lead_profile and get_lead_profile) for managing lead profiles, using FastMCP with Secret Manager-based authentication and supporting both stdio and HTTP transports.-
- FlicenseNot gradedqualityAmaintenanceEnables AI assistants to interact with HubSpot CRM data, providing tools to create and retrieve contacts, companies, activities, and conversations, plus semantic search across previously retrieved data. Built-in vector storage and caching help overcome HubSpot API limitations and improve response times.-
- FlicenseNot gradedqualityAmaintenanceEnables AI assistants to interact with HubSpot CRM data for creating and retrieving contacts, companies, activity, and conversations, with built-in vector storage and caching for faster semantic search.-