@revfleet/hscli 0.9.8 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/README.md +142 -13
- package/SECURITY.md +14 -3
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/cloud/cloud-run-caller-contract.d.ts +28 -0
- package/dist/cloud/cloud-run-caller-contract.js +35 -0
- package/dist/cloud/cloud-run-caller-contract.js.map +1 -0
- package/dist/cloud/main.d.ts +1 -0
- package/dist/cloud/main.js +15 -0
- package/dist/cloud/main.js.map +1 -0
- package/dist/cloud/server.d.ts +5 -0
- package/dist/cloud/server.js +133 -0
- package/dist/cloud/server.js.map +1 -0
- package/dist/commands/agent/access-baseline.d.ts +47 -0
- package/dist/commands/agent/access-baseline.js +238 -0
- package/dist/commands/agent/access-baseline.js.map +1 -0
- package/dist/commands/agent/index.js +99 -11
- package/dist/commands/agent/index.js.map +1 -1
- package/dist/commands/agent/recipes.d.ts +7 -1
- package/dist/commands/agent/recipes.js +57 -1
- package/dist/commands/agent/recipes.js.map +1 -1
- package/dist/commands/agent/reporting-joinability.d.ts +129 -0
- package/dist/commands/agent/reporting-joinability.js +412 -0
- package/dist/commands/agent/reporting-joinability.js.map +1 -0
- package/dist/commands/agent/runner.d.ts +1 -0
- package/dist/commands/agent/runner.js +181 -27
- package/dist/commands/agent/runner.js.map +1 -1
- package/dist/commands/audit/index.js +2 -1
- package/dist/commands/audit/index.js.map +1 -1
- package/dist/commands/auth/index.d.ts +6 -2
- package/dist/commands/auth/index.js +172 -57
- package/dist/commands/auth/index.js.map +1 -1
- package/dist/commands/backup/index.js +6 -8
- package/dist/commands/backup/index.js.map +1 -1
- package/dist/commands/crm/describe.js +3 -3
- package/dist/commands/crm/describe.js.map +1 -1
- package/dist/commands/crm/index.js +2 -0
- package/dist/commands/crm/index.js.map +1 -1
- package/dist/commands/crm/record-layouts.d.ts +3 -0
- package/dist/commands/crm/record-layouts.js +246 -0
- package/dist/commands/crm/record-layouts.js.map +1 -0
- package/dist/commands/crm/shared.d.ts +1 -0
- package/dist/commands/crm/shared.js +7 -3
- package/dist/commands/crm/shared.js.map +1 -1
- package/dist/commands/crm/sync.js +170 -27
- package/dist/commands/crm/sync.js.map +1 -1
- package/dist/commands/doctor/index.js +5 -5
- package/dist/commands/doctor/index.js.map +1 -1
- package/dist/commands/internal/index.js +20 -6
- package/dist/commands/internal/index.js.map +1 -1
- package/dist/commands/internal-session.d.ts +4 -0
- package/dist/commands/internal-session.js +27 -0
- package/dist/commands/internal-session.js.map +1 -1
- package/dist/commands/lists/index.js +18 -0
- package/dist/commands/lists/index.js.map +1 -1
- package/dist/commands/marketing/index.js +5 -5
- package/dist/commands/marketing/index.js.map +1 -1
- package/dist/commands/mcp.d.ts +2 -1
- package/dist/commands/mcp.js +49 -5
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/sales/index.js +2 -0
- package/dist/commands/sales/index.js.map +1 -1
- package/dist/commands/sales/views.d.ts +3 -0
- package/dist/commands/sales/views.js +263 -0
- package/dist/commands/sales/views.js.map +1 -0
- package/dist/commands/settings/index.js +20 -0
- package/dist/commands/settings/index.js.map +1 -1
- package/dist/commands/trace/index.js +11 -4
- package/dist/commands/trace/index.js.map +1 -1
- package/dist/commands/workflows/index.js +2 -0
- package/dist/commands/workflows/index.js.map +1 -1
- package/dist/commands/workflows/internal.d.ts +3 -0
- package/dist/commands/workflows/internal.js +332 -0
- package/dist/commands/workflows/internal.js.map +1 -0
- package/dist/core/auth.d.ts +18 -2
- package/dist/core/auth.js +96 -3
- package/dist/core/auth.js.map +1 -1
- package/dist/core/bounded-response.d.ts +2 -0
- package/dist/core/bounded-response.js +37 -0
- package/dist/core/bounded-response.js.map +1 -0
- package/dist/core/capabilities.d.ts +8 -0
- package/dist/core/capabilities.js +31 -3
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/http.d.ts +16 -0
- package/dist/core/http.js +151 -35
- package/dist/core/http.js.map +1 -1
- package/dist/core/output.js +20 -4
- package/dist/core/output.js.map +1 -1
- package/dist/core/permissions.js +15 -1
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/policy.js +8 -0
- package/dist/core/policy.js.map +1 -1
- package/dist/core/request-path.d.ts +13 -0
- package/dist/core/request-path.js +90 -0
- package/dist/core/request-path.js.map +1 -0
- package/dist/core/schema-cache.js +6 -2
- package/dist/core/schema-cache.js.map +1 -1
- package/dist/core/vault.js +10 -5
- package/dist/core/vault.js.map +1 -1
- package/dist/core/version.d.ts +1 -0
- package/dist/core/version.js +3 -0
- package/dist/core/version.js.map +1 -0
- package/dist/mcp/compat-hubspot.d.ts +5 -14
- package/dist/mcp/compat-hubspot.js +19 -16
- package/dist/mcp/compat-hubspot.js.map +1 -1
- package/dist/mcp/diagnostics.d.ts +157 -0
- package/dist/mcp/diagnostics.js +200 -0
- package/dist/mcp/diagnostics.js.map +1 -0
- package/dist/mcp/ecosystem.d.ts +107 -0
- package/dist/mcp/ecosystem.js +127 -0
- package/dist/mcp/ecosystem.js.map +1 -0
- package/dist/mcp/ext-tools.d.ts +4 -3
- package/dist/mcp/ext-tools.js +20 -8
- package/dist/mcp/ext-tools.js.map +1 -1
- package/dist/mcp/profiles.d.ts +9 -0
- package/dist/mcp/profiles.js +38 -4
- package/dist/mcp/profiles.js.map +1 -1
- package/dist/mcp/server.d.ts +118 -1
- package/dist/mcp/server.js +67 -10
- package/dist/mcp/server.js.map +1 -1
- package/docs/CAPABILITY_LIBRARY.md +7 -1
- package/docs/CLOUD_RUN.md +188 -0
- package/docs/COMMAND_TREE.md +20 -6
- package/docs/COMPARISON.md +14 -6
- package/docs/COOKBOOK.md +46 -2
- package/docs/INTERNAL-ENDPOINTS-DISCOVERY.md +59 -0
- package/docs/INTERNAL-ENDPOINTS.md +13 -0
- package/docs/INTERNAL_ENDPOINTS.md +23 -5
- package/docs/MCP.md +48 -6
- package/docs/MCP_TOOL_CATALOG.md +188 -186
- package/docs/OPERATIONAL_PLAYBOOKS.md +9 -8
- package/docs/PORTAL_BACKUP.md +26 -2
- package/docs/RELEASE_GOVERNANCE.md +7 -0
- package/docs/SAFETY_MODEL.md +23 -4
- package/docs/TUTORIALS/secure-agent-writes.md +3 -2
- package/docs/VALIDATION_BOUNDARIES.md +101 -0
- package/docs/policy-templates/compliance-strict.json +7 -0
- package/examples/cloud-run-mcp-caller.mjs +71 -0
- package/package.json +11 -8
package/docs/SAFETY_MODEL.md
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
4. **Policy-first execution**: optional policy file can deny writes/deletes or require change-ticket context.
|
|
10
10
|
5. **Graceful API resilience**: timeout + retry skeleton handles transient failures and rate limits.
|
|
11
11
|
|
|
12
|
+
`policy validate` and `policy show-matching` are diagnostics, not execution
|
|
13
|
+
authorization. A matching allow rule still has to pass every active runtime
|
|
14
|
+
gate. Rules with `rateLimit.maxPerHour` fail closed with
|
|
15
|
+
`POLICY_RATE_LIMIT_UNSUPPORTED` until a durable counter is available; they do
|
|
16
|
+
not simulate hourly enforcement.
|
|
17
|
+
|
|
12
18
|
## Dry-run Contract
|
|
13
19
|
- Applies to all mutating commands routed through `maybeWrite`.
|
|
14
20
|
- In dry-run mode, command returns:
|
|
@@ -19,14 +25,24 @@
|
|
|
19
25
|
- No network mutation request is sent.
|
|
20
26
|
|
|
21
27
|
## Rate Limit & Retry Baseline
|
|
22
|
-
- Retry
|
|
23
|
-
|
|
24
|
-
-
|
|
28
|
+
- Retry-eligible reads (`GET`, plus a `POST` explicitly marked as a read) retry
|
|
29
|
+
on `429`, `5xx`, and network errors.
|
|
30
|
+
- Mutations (`POST`/`PATCH`/`PUT`/`DELETE` unless explicitly classified as a
|
|
31
|
+
read) do not retry by default, including on `429`. A non-retried mutation
|
|
32
|
+
`429` returns `HTTP_RETRY_DISABLED`; ambiguous mutation timeout, network, or
|
|
33
|
+
`5xx` outcomes return `HTTP_OUTCOME_UNKNOWN` for fresh server-state
|
|
34
|
+
reconciliation.
|
|
35
|
+
- A retry-eligible read forced to `retryMode: never` returns
|
|
36
|
+
`HTTP_RETRY_DISABLED` for a `429` or `5xx` response.
|
|
37
|
+
- An internal `retryMode: idempotent` opt-in is valid only with a verified
|
|
38
|
+
server-side idempotency contract. An `Idempotency-Key` header alone is not
|
|
39
|
+
that proof.
|
|
40
|
+
- Retry-eligible requests honor `Retry-After`, use capped exponential backoff,
|
|
41
|
+
and can return `HTTP_RETRY_EXHAUSTED` after their retry budget.
|
|
25
42
|
- Share observed HubSpot rate-limit state across clients in the same process by profile + API base URL.
|
|
26
43
|
- Enforce HubSpot `X-HubSpot-RateLimit-Secondly`, rolling interval, and daily remaining headers before the next request.
|
|
27
44
|
- Stop before daily quota exhaustion with `RATE_LIMIT_DAILY_EXHAUSTED`.
|
|
28
45
|
- Enforce request timeout (`30s`) to prevent hung command invocations.
|
|
29
|
-
- Surface retry exhaustion as `HTTP_RETRY_EXHAUSTED`.
|
|
30
46
|
|
|
31
47
|
## Profiles & Access
|
|
32
48
|
- Profiles isolate tokens per environment/workspace.
|
|
@@ -38,3 +54,6 @@
|
|
|
38
54
|
- Explicit JSON payload required for create/update.
|
|
39
55
|
- Unknown command errors are structured and non-destructive.
|
|
40
56
|
- API paths are constrained to approved HubSpot scopes.
|
|
57
|
+
- A configured profile or stored portal ID is local metadata, not proof of the
|
|
58
|
+
live target. Confirm the target in the approved operation before sensitive
|
|
59
|
+
work.
|
|
@@ -89,7 +89,7 @@ Rules are evaluated **first-match-wins**, so order matters. `defaultAction: "den
|
|
|
89
89
|
hscli policy validate ./policy.json
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
This parses the file and flags structural issues (invalid actions, malformed `window.hours`, rules with no `match` block that would match everything).
|
|
92
|
+
This parses the file and flags structural issues (invalid actions, malformed `window.hours`, rules with no `match` block that would match everything). `valid: true` means the static check passed; it does not authorize a future request or prove its target portal.
|
|
93
93
|
|
|
94
94
|
## Step 4 — dry-run rule matching
|
|
95
95
|
|
|
@@ -99,6 +99,7 @@ Before enforcing, confirm the right rule fires for the requests you expect:
|
|
|
99
99
|
hscli --policy-file ./policy.json \
|
|
100
100
|
policy show-matching POST /crm/v3/objects/contacts
|
|
101
101
|
# → matched: allow-crm-writes-with-ticket, effectiveAction: allow
|
|
102
|
+
# Diagnostic only: the real command still needs every runtime gate.
|
|
102
103
|
|
|
103
104
|
hscli --policy-file ./policy.json \
|
|
104
105
|
policy show-matching DELETE /crm/v3/objects/contacts/123
|
|
@@ -117,7 +118,7 @@ Either pass `--policy-file` on every invocation, or set it globally:
|
|
|
117
118
|
export HSCLI_POLICY_FILE=$PWD/policy.json
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
From now on,
|
|
121
|
+
From now on, hscli write paths wired to the policy control evaluate the active policy before their HTTP request. A denied guarded write fails *before* that request is issued. This is not a claim that every read, local command, or MCP tool is policy-enforced. An allowed diagnostic match remains subject to `--force`, tickets, approvals, time windows, and all other runtime controls.
|
|
121
122
|
|
|
122
123
|
Try a denied operation:
|
|
123
124
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Validation Boundaries and Operational Limits
|
|
2
|
+
|
|
3
|
+
hscli is designed to make evidence visible, not to turn a local configuration
|
|
4
|
+
or a green test suite into proof about a HubSpot portal. This page records the
|
|
5
|
+
boundary between local checks, CI, and a separately approved live rehearsal.
|
|
6
|
+
|
|
7
|
+
| Evidence layer | Can establish | Does not establish |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Source and local gates | Command parsing, deterministic behavior under synthetic fixtures, type safety, lint, build output, and locked dependency resolution | HubSpot endpoint availability, portal state, scopes, subscriptions, or browser-session access |
|
|
10
|
+
| CI from a clean checkout | That the declared automated gates reproduce from the committed inputs | That an operator's local profile, policy file, cookies, or target portal are current |
|
|
11
|
+
| Approved live rehearsal | The specific read or write path, credential, portal, and time-window actually exercised | A blanket guarantee for other portals, identities, plans, feature flags, or later HubSpot changes |
|
|
12
|
+
|
|
13
|
+
Keep these layers separate in release notes and handoffs. A successful local or
|
|
14
|
+
CI check is never a substitute for an approved live rehearsal, and a live
|
|
15
|
+
rehearsal must be reported with its scope rather than generalized to all
|
|
16
|
+
portals.
|
|
17
|
+
|
|
18
|
+
## Profile and portal identity
|
|
19
|
+
|
|
20
|
+
`--profile` selects a local credential configuration. A stored or displayed
|
|
21
|
+
`portalId` is local metadata and must not be treated as attestation of the
|
|
22
|
+
currently authenticated target. `auth whoami` reads that saved local profile,
|
|
23
|
+
including its historical `identityVerified` value; it does not call HubSpot and
|
|
24
|
+
is not live identity proof. Before a sensitive live operation, use only a
|
|
25
|
+
remote target check explicitly supported and authorized by that operation,
|
|
26
|
+
compare it with the approved target, and keep source, sandbox, and production
|
|
27
|
+
profiles distinct.
|
|
28
|
+
|
|
29
|
+
This applies equally to agent-run evidence: a profile name and declared policy
|
|
30
|
+
path describe local execution context. They do not prove a live target, and
|
|
31
|
+
they do not cryptographically authenticate older local artifacts.
|
|
32
|
+
|
|
33
|
+
## Agent resume migration
|
|
34
|
+
|
|
35
|
+
Resumable agent runs require a manifest and checkpoint carrying the same run
|
|
36
|
+
ID, plus the retained stdout/stderr files and matching completion events for
|
|
37
|
+
every step that would be skipped. Historical checkpoints without that run-ID
|
|
38
|
+
binding are intentionally not resumable. Preserve them as legacy evidence if
|
|
39
|
+
needed, then start a new output directory without `--resume`; do not add a run
|
|
40
|
+
ID by hand or relabel the old steps as fresh evidence.
|
|
41
|
+
|
|
42
|
+
## Policy diagnostics and enforcement
|
|
43
|
+
|
|
44
|
+
`hscli policy validate` parses and statically checks a policy file. `hscli
|
|
45
|
+
policy show-matching` evaluates a hypothetical method/path against that file.
|
|
46
|
+
They are useful diagnostics, but neither command executes the request or grants
|
|
47
|
+
permission. In particular, an `effectiveAction: allow` result still needs the
|
|
48
|
+
actual command to pass all execution-time controls: canonical path handling,
|
|
49
|
+
the active policy, `--force` for writes, tickets, approvals, windows, and any
|
|
50
|
+
other applicable gates.
|
|
51
|
+
|
|
52
|
+
Policy rules that declare `rateLimit.maxPerHour` are not backed by a durable
|
|
53
|
+
hourly counter. The reviewed implementation fails closed with
|
|
54
|
+
`POLICY_RATE_LIMIT_UNSUPPORTED` rather than pretending that the limit was
|
|
55
|
+
enforced. Operators must remove that rule only after choosing and reviewing an
|
|
56
|
+
appropriate external or durable rate-control mechanism.
|
|
57
|
+
|
|
58
|
+
Built-in policy templates are starting points. Extracting a template creates a
|
|
59
|
+
separate local policy file; later changes to a template, including a newly
|
|
60
|
+
covered HTTP method such as `PUT`, do not retrofit an already extracted or
|
|
61
|
+
active policy. Re-extract or edit the intended policy, then validate and review
|
|
62
|
+
it before use.
|
|
63
|
+
|
|
64
|
+
## Backup completeness
|
|
65
|
+
|
|
66
|
+
`hscli backup plan` is plan-only: it makes no HubSpot request and creates no
|
|
67
|
+
archive. Its coverage model is a checklist, not proof that every asset was
|
|
68
|
+
captured. Capture execution still has known limitations: some reads materialize
|
|
69
|
+
pages in memory, and a complete durable pagination/resume reconciliation is not
|
|
70
|
+
yet guaranteed across every backup surface.
|
|
71
|
+
|
|
72
|
+
Treat a backup as partial until the operator reconciles object counts and
|
|
73
|
+
cursors, records unavailable or permission-blocked surfaces, and verifies the
|
|
74
|
+
resulting capture manifest. Do not call a plan, a successful command, or an
|
|
75
|
+
archive complete merely because no local error was emitted.
|
|
76
|
+
|
|
77
|
+
The record `crm sync pull` fields have deliberately narrower meanings:
|
|
78
|
+
|
|
79
|
+
- `complete` and `captureScope.paginationExhausted` mean this traversal reached
|
|
80
|
+
the end of its pagination cursor; they do not prove a complete portal archive.
|
|
81
|
+
- `captureScope.objectSnapshotComplete` means a fresh, unfiltered list
|
|
82
|
+
traversal started at the initial cursor and exhausted pagination for that
|
|
83
|
+
object. It does not prove every property, association, file, report, or other
|
|
84
|
+
portal surface was captured.
|
|
85
|
+
- The backup plan's `--full-snapshot --max-pages all` record commands retain all
|
|
86
|
+
fetched pages in memory. A full snapshot refuses an existing state file or
|
|
87
|
+
output file rather than silently mixing old and new results.
|
|
88
|
+
|
|
89
|
+
Sync checkpoint reuse is separate from agent resume. A reusable sync state is
|
|
90
|
+
bound to the configured `portalId`, local profile name, object type, mode,
|
|
91
|
+
`since` value, limit, and query fingerprint. Legacy states without that binding
|
|
92
|
+
are refused; this prevents cursor reuse across a different declared scope but
|
|
93
|
+
does not attest a live target.
|
|
94
|
+
|
|
95
|
+
## Release language
|
|
96
|
+
|
|
97
|
+
Use precise, time-bounded claims. For example, “`npm audit` reported zero
|
|
98
|
+
advisories at verification time” describes an observed result; it does not mean
|
|
99
|
+
that future advisory databases will remain empty. Likewise, a reviewed source
|
|
100
|
+
diff is not merged, deployed, published, or live-tested until those separate
|
|
101
|
+
actions have been explicitly authorized and evidenced.
|
|
@@ -29,6 +29,13 @@
|
|
|
29
29
|
"action": "allow",
|
|
30
30
|
"requireChangeTicket": true,
|
|
31
31
|
"window": { "tz": "US/Eastern", "hours": "09-17", "days": "mon-fri" }
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "puts-require-change-ticket-and-hours",
|
|
35
|
+
"match": { "method": "PUT", "path": "**" },
|
|
36
|
+
"action": "allow",
|
|
37
|
+
"requireChangeTicket": true,
|
|
38
|
+
"window": { "tz": "US/Eastern", "hours": "09-17", "days": "mon-fri" }
|
|
32
39
|
}
|
|
33
40
|
]
|
|
34
41
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
|
|
4
|
+
const READ_ONLY_TOOLS = new Set([
|
|
5
|
+
"hscli_service_info",
|
|
6
|
+
"crm_properties_list",
|
|
7
|
+
"crm_pipelines_list",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
function serviceEndpoint(cloudRunServiceUrl, label) {
|
|
11
|
+
const service = new URL(cloudRunServiceUrl);
|
|
12
|
+
if (service.protocol !== "https:" || service.pathname !== "/" || service.search || service.hash || service.username || service.password || service.port) {
|
|
13
|
+
throw new Error(`${label} must be an HTTPS base URL without a path`);
|
|
14
|
+
}
|
|
15
|
+
return service.origin;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Server-only example: its authenticatedSubject and portalId must come from the
|
|
20
|
+
* web service's own authenticated request context, not from browser headers,
|
|
21
|
+
* query parameters or tool arguments. resolveAuthorizedPortal is responsible
|
|
22
|
+
* for authorizing that pair and obtaining short-lived server-held HubSpot data.
|
|
23
|
+
*/
|
|
24
|
+
export async function callAuthorizedHostedMcp({
|
|
25
|
+
cloudRunServiceUrl,
|
|
26
|
+
mcpTargetUrl,
|
|
27
|
+
authenticatedSubject,
|
|
28
|
+
portalId,
|
|
29
|
+
toolName,
|
|
30
|
+
toolArguments,
|
|
31
|
+
resolveAuthorizedPortal,
|
|
32
|
+
getGoogleIdToken,
|
|
33
|
+
fetch,
|
|
34
|
+
}) {
|
|
35
|
+
// `cloudRunServiceUrl` is the canonical service URL for the Google ID-token
|
|
36
|
+
// audience. `mcpTargetUrl` is the freshly checked tagged candidate URL; do
|
|
37
|
+
// not infer it from a shared tag without checking it still names this revision.
|
|
38
|
+
const audience = serviceEndpoint(cloudRunServiceUrl, "Cloud Run service URL");
|
|
39
|
+
const mcpUrl = new URL("/mcp", serviceEndpoint(mcpTargetUrl, "MCP target URL"));
|
|
40
|
+
if (!READ_ONLY_TOOLS.has(toolName)) throw new Error("HOSTED_TOOL_NOT_ALLOWED");
|
|
41
|
+
|
|
42
|
+
// Do this before requesting a Google token or constructing an HTTP transport.
|
|
43
|
+
const hubSpotContext = await resolveAuthorizedPortal({ authenticatedSubject, portalId });
|
|
44
|
+
if (!hubSpotContext || hubSpotContext.portalId !== portalId) throw new Error("PORTAL_NOT_AUTHORIZED");
|
|
45
|
+
if (!/^[0-9]+$/.test(hubSpotContext.portalId) || !["eu1", "na1"].includes(hubSpotContext.hublet) || !hubSpotContext.accessToken) {
|
|
46
|
+
throw new Error("INVALID_AUTHORIZED_PORTAL_CONTEXT");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// An attached Cloud Run identity normally implements this with ADC's
|
|
50
|
+
// google-auth-library.getIdTokenClient(audience). The ID token is not a
|
|
51
|
+
// HubSpot credential and is sent in a distinct Cloud Run IAM header.
|
|
52
|
+
const googleIdToken = await getGoogleIdToken(audience);
|
|
53
|
+
if (!googleIdToken) throw new Error("GOOGLE_ID_TOKEN_REQUIRED");
|
|
54
|
+
const client = new Client({ name: "authorized-web-backend", version: "example" });
|
|
55
|
+
try {
|
|
56
|
+
await client.connect(new StreamableHTTPClientTransport(mcpUrl, {
|
|
57
|
+
fetch,
|
|
58
|
+
requestInit: {
|
|
59
|
+
headers: {
|
|
60
|
+
"X-Serverless-Authorization": `Bearer ${googleIdToken}`,
|
|
61
|
+
"X-HubSpot-Access-Token": hubSpotContext.accessToken,
|
|
62
|
+
"X-HubSpot-Portal-Id": hubSpotContext.portalId,
|
|
63
|
+
"X-HubSpot-Hublet": hubSpotContext.hublet,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
return await client.callTool({ name: toolName, arguments: toolArguments });
|
|
68
|
+
} finally {
|
|
69
|
+
await client.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revfleet/hscli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "HubSpot operations and migration control plane for humans and agents, with deterministic runbooks, policy guardrails, and auditable evidence.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"docs:mcp": "npm run build && node scripts/generate-mcp-catalog.mjs",
|
|
19
19
|
"docs:mcp:check": "npm run build && node scripts/generate-mcp-catalog.mjs --check",
|
|
20
20
|
"release:checksums": "node scripts/release-checksums.mjs",
|
|
21
|
-
"audit": "npm audit --audit-level=
|
|
21
|
+
"audit": "npm audit --audit-level=low",
|
|
22
22
|
"release:verify": "npm run typecheck && npm run lint && npm test && npm run audit && npm run build && npm run release:checksums && shasum -a 256 -c release/checksums.sha256",
|
|
23
|
-
"prepublishOnly": "npm run
|
|
23
|
+
"prepublishOnly": "npm run release:verify",
|
|
24
24
|
"clean": "rm -rf dist"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"docs/TUTORIALS/*.md",
|
|
63
63
|
"docs/POLICY_EXAMPLE.json",
|
|
64
64
|
"docs/policy-templates",
|
|
65
|
+
"examples/cloud-run-mcp-caller.mjs",
|
|
65
66
|
"brand/readme-hero.svg",
|
|
66
67
|
"README.md",
|
|
67
68
|
"CONTRIBUTING.md",
|
|
@@ -73,20 +74,22 @@
|
|
|
73
74
|
"access": "public"
|
|
74
75
|
},
|
|
75
76
|
"dependencies": {
|
|
76
|
-
"@modelcontextprotocol/sdk": "
|
|
77
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
77
78
|
"commander": "^14.0.3",
|
|
78
79
|
"zod": "^4.3.6"
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|
|
81
82
|
"@eslint/js": "^10.0.1",
|
|
82
|
-
"@types/node": "^
|
|
83
|
-
"eslint": "^10.0
|
|
84
|
-
"tsx": "^4.
|
|
83
|
+
"@types/node": "^26.1.1",
|
|
84
|
+
"eslint": "^10.7.0",
|
|
85
|
+
"tsx": "^4.23.1",
|
|
85
86
|
"typescript": "^6.0.3",
|
|
86
|
-
"typescript-eslint": "^8.
|
|
87
|
+
"typescript-eslint": "^8.64.0",
|
|
87
88
|
"vitest": "^4.1.0"
|
|
88
89
|
},
|
|
89
90
|
"overrides": {
|
|
91
|
+
"hono": "^4.13.0",
|
|
92
|
+
"ip-address": "^10.4.0",
|
|
90
93
|
"path-to-regexp": "^8.4.2"
|
|
91
94
|
}
|
|
92
95
|
}
|