Skip to main content
Glama
bsmahi

LQABR HubSpot MCP Server

by bsmahi

LQABR_MCP

Der zentrale LQABR HubSpot MCP-Server. Basierend auf FastMCP und eigenständig — keine Abhängigkeit vom LQABR-Mono-Repo.

RUNNING.md — wie man es ausführt: Einrichtung, Anmeldedaten, beide Transporte, Agentgateway, Fehlerbehebung. CONSUMING.md — wie die E-Mail-/Sprach-/Planungsagenten es aufrufen: ADK McpToolset, Authentifizierung, der Wire-Contract, Tool-Scoping. Diese Datei ist die Design-Begründung; diese beiden sind die Verfahren.

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

Dies ist ein Fork, kein Umzug

hubspot_mcp/ ist eine Kopie der Implementierung des Mono-Repos. Nur die Importzeilen wurden umgeschrieben; keine Logik, Feldname oder HubSpot-Eigenschaftsname geändert. Durch Diff verifiziert — die einzigen unterschiedlichen Zeilen in allen neun Dateien sind:

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

Das Mono-Repo benötigt weiterhin seine Kopie. Nicht löschen.

Modul

Auch verwendet von

lqabr_core.obs

8 Dateien in lead_profile/src, 2 in text_voice/src, lqabr_core/crm

lqabr_core.leadgen.*

12 lead_profile-Dateien inkl. call_mcp.py, plus evals/run_eval.py

lqabr_core.leadgen.secrets

lead_profile model.py, 2 Testdateien

Die Konsequenz: Es gibt nun zwei Schreibpfade zu HubSpot mit denselben Feldnamen und denselben HubSpot-Eigenschaftsnamen — und der Datenvertrag besagt, dass diese Namen der Vertrag sind. Jede Korrektur an crm.py, jede Authentifizierungsänderung, jede Umbenennung einer HubSpot-Eigenschaft muss von Hand an beiden Stellen angewendet werden. Nichts erzwingt dies. Planen Sie das ein, oder planen Sie, eine Seite auszumustern.

Related MCP server: hubspot-mcp-server

Warum dies außerhalb des Mono-Repos lebt

Das LQABR-Repo hat ein Top-Level-Paket, das buchstäblich mcp an seiner Wurzel heißt, was das mcp-SDK überschattet, von dem FastMCP abhängt. Das Ausführen von innerhalb des Repos bricht FastMCPs eigene Importe.

Fügen Sie die LQABR-Repo-Wurzel nicht zu PYTHONPATH hinzu.

Bibliothek: FastMCP, nicht das offizielle SDK

Die Abhängigkeit ist fastmcp>=3.4.7. Fügen Sie mcp>=2.0 nicht hinzu — die beiden schließen sich gegenseitig aus:

  • fastmcp 3.4.7 pinnt transitiv mcp<2.0,>=1.24.0 (es installiert mcp 1.29).

  • MCPServer, die Serverklasse des offiziellen SDKs, existiert nur in mcp>=2.0.

Ein Projekt kann also entweder FastMCP oder MCPServer verwenden, niemals beide. Dieses verwendet FastMCP. Nichts hier importiert MCPServer.

Zwei Konsequenzen, die es wert sind, zu wissen:

  • Transportnamen unterscheiden sich. FastMCPs HTTP-Transport ist "http" ("streamable-http" wird als Alias akzeptiert) und das Endpunkt-Pfad-Kwarg ist path=. Das offizielle SDK schreibt das streamable_http_path=.

  • Typfelder unterscheiden sich. Da mcp auf 1.x gepinnt ist, verwenden die gebündelten Typen camelCase: Tool.inputSchema, nicht input_schema.

Das lqabr_core/leadgen/server.py des Mono-Repos verwendet immer noch MCPServer. Das ist nun eine zweite Abweichung zwischen den beiden Codebasen, zusätzlich zum Fork.

Ausführen

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

Anmeldedaten

tools/list benötigt nichts. Ein Tool-Aufruf benötigt das HubSpot-Token, und das kommt aus Secret Manager — Kontext §7.6 / CLAUDE.md §5: Geheimnisse werden nie hartcodiert und nie committet.

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> synchronisiert genau diesen zusätzlichen Satz, also deinstalliert --extra gcp allein pytest. test_server.py läuft als CLI ohne pytest, aber uv run pytest benötigt es offensichtlich.

.env enthält keine geheimen Werte — nur HUBSPOT_AUTH_MODE, LQABR_SECRET_PROJECT und die ID des Geheimnisses. Das Token selbst wird zur Laufzeit über die Secret Manager API abgerufen, im Speicher gehalten, nie protokolliert (die Audit-Zeile zeichnet nur Länge und die letzten vier Zeichen auf) und für 900s zwischengespeichert, sodass eine Rotation kein erneutes Deployment erfordert.

auth.py und secrets.py schlagen beide geschlossen fehl — nicht gesetzt bedeutet einen expliziten Fehler, niemals einen stillen Standardwert.

LQABR_SECRET_BACKEND=env existiert als letzter Ausweg für Offline-Arbeit oder CI. secrets.py beschränkt es auf "lokale Entwicklung, CI und Tests nur" und es gibt keinen automatischen Fallback darauf — Sie müssen es eingeben. Es legt ein Live-Geheimnis in einer Datei auf der Festplatte ab. Setzen Sie es niemals in Cloud Run.

Jedes Flag hat einen Umgebungsvariablen-Standardwert (MCP_TRANSPORT, MCP_HOST, PORT, MCP_PATH), sodass der Container ohne Argumente startet — Cloud Run injiziert PORT.

Tests

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

pytest.ini setzt hubspot-crm-mcp-server/ in den Pfad, sodass import hubspot_mcp aufgelöst wird — der Ordner selbst kann aufgrund des Bindestrichs kein Paket sein.

Der Vertrag hat ZEHN Felder, nicht neun

LeadProfile trägt zehn: die neun, die jeder dokumentiert, plus contact_name (hinzugefügt für die Vorname/Nachname-Zuordnung). Das test_wrapper_shape_is_the_nine_fields_plus_ids des Mono-Repos behauptete immer noch 9 und war dort fehlgeschlagen — schema.py ist byte-identisch, also hat dieses Projekt es geerbt.

Gelöst am 2026-08-18: Der Code war richtig, die Zahl war veraltet. Der Test wurde in test_wrapper_shape_is_the_contract_fields_plus_ids umbenannt und behauptet nun die Feld-Namen anstelle einer Anzahl, sodass die nächste Hinzufügung mit etwas Lesbarem fehlschlägt.

Die gleiche Korrektur ist dem Mono-Repo noch geschuldet — diese Behauptung ist dort unverändert und immer noch rot. Die Dokumente, die "9 Felder" sagen, sollten ebenfalls korrigiert werden.

Testen eines bereitgestellten Servers

# 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

Schreibgeschützt von Design: Es ruft nie upsert_lead_profile auf. Sendet X-LQABR-Run-Id, damit die Audit-Logs des Servers den Aufruf zuordnen (der B10-Fix).

Die ID-Token-Zielgruppe ist die Service-Basis-URL ohne /mcp. Das Skript entfernt es für Sie.

Der Client verwendet fastmcp.Client, der den Initialize-Handshake übernimmt, sodass es in dieser Datei keine Session-Infrastruktur gibt. Header reiten auf einem StreamableHttpTransport. Dies entspricht nun der Bibliothek des Referenzbeispiels.

Bereitstellen auf Cloud Run

Zweistufiger uv-Build, nicht-root mcp-Benutzer, PID 1 ist Python, sodass SIGTERM sauber abfließt. In sich geschlossen: kein Geschwisterordner, keine git-Abhängigkeit.

docker build -t lqabr-mcp-server .

Committen Sie eine uv.lock und schalten Sie die Synchronisierung vor der Produktion auf --frozen um.

Dann S5–S8: Erstellen Sie mcp-server-sa, gewähren Sie secretmanager.secretAccessor auf lqabr-hubspot-access-token, deployen Sie --no-allow-unauthenticated, gewähren Sie den drei Agenten-Dienstkonten roles/run.invoker, richten Sie test_server.py darauf aus.

Verfügbare Tools

Tool

Richtung

Notizen

upsert_lead_profile

schreiben

Company-Upsert → Contact-Upsert → Assoziation. Idempotent.

get_lead_profile

lesen

Vertragsfelder + contact_hs_id + company_hs_id.

Dedup: Contact auf employee_id, Company auf company_id. E-Mail lebt in der benutzerdefinierten email_id-Eigenschaft.

Offener Punkt. Der registrierte Tool-Name ist upsert_lead_profile (Singular), aber die Design-Dokumente, Session-Übergabe und Projektanweisungen sagen alle upsert_lead_profiles (Plural), und die zugrunde liegende Funktion ist Plural. Dies ist ein Wire-Contract — klären Sie es, bevor ein Client sich anschließt.

Anmeldedaten

Zwei, niemals verwechselt:

  • Google-ID-Token beweist Agent → dieser Server (Cloud Run Service-zu-Service).

  • HubSpot M2M-Token beweist dieser Server → HubSpot, wird pro Aufruf innerhalb des Servers erstellt. Aufrufer sehen es nie.

Dieser Service ist der alleinige Inhaber der HubSpot-Anmeldedaten.

Verifiziert

In einer sauberen venv, die nur fastmcp 3.4.7 (das mcp 1.29 mitbrachte), requests und pytest enthält — mit abwesendem lqabr_core (bestätigt ModuleNotFoundError) und abwesendem MCPServer (bestätigt ImportError):

  • null lqabr_core-Importe irgendwo im Projekt

  • portierte Testsuite: 61 bestanden, 0 fehlgeschlagen

  • stdio — tools/list gibt beide Tools zurück, PYTHONPATH vollständig entfernt

  • --transport http — bindet host/port/path, vollständige fastmcp.Client-Session

  • --transport streamable-http — Alias akzeptiert, bedient denselben Endpunkt

  • test_server.py — BESTANDEN gegen den laufenden Server, Exit 0

  • test_server.py unter pytest — 3 bestanden mit Server, 3 übersprungen ohne

  • Live call_tool erreichte die echte Kette: transport → tool → crm.py → auth.py → secrets.py, fehlschlagend nur an den absichtlichen AuthConfigError/SecretConfigError-Wächtern (keine GCP-Konfiguration in der Testumgebung). Die Verkabelung ist Ende-zu-Ende nachgewiesen.

Nicht verifiziert: Das Docker-Image wurde nie gebaut — kein Docker-Daemon war verfügbar, wo diese Dateien zusammengestellt wurden. Nichts hat echtes HubSpot berührt; jeder Test verwendet einen 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.
    -