@elitedcs/ghl-mcp 3.19.1 → 3.23.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/index.js +114 -36
  3. package/package.json +8 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.23.0 — `update_calendar` can assign team members again (Henry fix, part 2)
4
+
5
+ **Critical fix. Tool count unchanged (212 across 43 modules). The `teamMembers` parameter is back on `update_calendar` — and actually persists this time — with a typed schema that surfaces GHL's strict validation up front.**
6
+
7
+ Reported by Henry Boulton, 2026-05-26.
8
+
9
+ **Root cause.** Same UI-vs-API pattern we hit in v3.22.0 (forms) and v3.15.0 (email templates). v3.21.0's diagnosis ("GHL silently drops `teamMembers` on PUT, no public-API path exists") was based on testing against an Event calendar — that calendar TYPE doesn't have team members, so GHL accepts the call and ignores the field. On round-robin and class-booking calendars the field is meaningful, and PUT validates it strictly: `priority` must be exactly `0`, `0.5`, or `1` (anything else 422s), `locationConfigurations` needs at least one entry, and `userId` / `selected` / `isPrimary` must be present. Henry's original attempt likely either targeted the wrong calendar type or passed a non-enum priority.
10
+
11
+ **Fix.** `teamMembers` is back on `update_calendar`, typed via an exported `CalendarTeamMemberSchema` that mirrors GHL's validation:
12
+
13
+ - `priority` constrained to `z.union([z.literal(0), z.literal(0.5), z.literal(1)])` — invalid values fail at the MCP layer with a clear error instead of an opaque GHL 422.
14
+ - `locationConfigurations` requires `min(1)` and a non-negative integer `position`.
15
+ - Tool description spells out that team-member assignment only applies to calendar types that support it (round_robin, class_booking); on Event calendars the field is silently ignored by GHL — that's the type, not us.
16
+
17
+ End-to-end verification 2026-05-26 against MCP Testing: create round_robin → assign user → update via PUT (priority 0.5 → 1, location string change) → GET back the new shape → cleanup.
18
+
19
+ Five new tests in `src/tools/tool-payloads.test.ts` lock the schema in.
20
+
21
+ ## 3.22.0 — `update_form` works again (Henry fix)
22
+
23
+ **Critical fix. Tool count unchanged (212 across 43 modules). The `update_form` tool, which v3.21.0 marked "currently unavailable," is back — wired to the live save endpoint and verified end-to-end against MCP Testing.**
24
+
25
+ Reported by Henry Boulton, 2026-05-26.
26
+
27
+ **Root cause.** v3.21.0's diagnosis was half right and half wrong. The half that was right: the v2 form builder UI does run in a cross-origin iframe (`leadgen-apps-form-survey-builder.leadconnectorhq.com`) and the UI does read form state through Firestore — top-frame network capture only sees a Firestore `Listen` channel, never a REST save. That's exactly what made the route look gone. The half that was wrong: we concluded the REST save route therefore *didn't exist* and the tool needed a Firestore-client integration. It does exist. The new UI just doesn't drive it. Same UI-vs-API divergence we saw with email templates in v3.15.0 ([reference_email_template_firestore](https://github.com/drjerryrelth/ghl-command-mcp/blob/main/CHANGELOG.md#3150)) — the REST routes survive even when the editor moves to Firestore.
28
+
29
+ **Fix.** `update_form` now calls `POST /forms/{formId}?locationId={loc}` with body exactly `{name, formData}`. Verified shape constraints (server rejects everything else with 422 "property X should not exist"):
30
+
31
+ - `locationId` belongs in the query string only — putting it in the body fails.
32
+ - `name` and `formData` are both required together — one without the other 422s.
33
+ - `_id`, `deleted`, `productType`, `dateAdded`, `dateUpdated`, `source`, `updatedBy`, `version`, `updatedAt`, `versionHistory` must not appear in the body — `get_form_full` → pass through fails, so callers should pass only the `formData` they want to write.
34
+
35
+ If `name` is omitted, the tool pre-fetches the current form (one extra GET) so the existing name is preserved. Body and path are factored into pure `buildUpdateFormBody` / `buildUpdateFormPath` helpers and locked in by `src/tools/tool-payloads.test.ts` (3 new tests) — a refactor can't silently regress the tool back to v3.21.0's "unavailable" state.
36
+
37
+ End-to-end verification 2026-05-26: read form (version 5) → mutate first field's `placeholder` → save (201) → re-read (placeholder persisted) → restore. MCP Testing sandbox left clean.
38
+
39
+ ## 3.21.0 — Workflow Builder now loads for npm-install buyers (Henry + Ryan fix)
40
+
41
+ **Critical fix. Tool count unchanged (212 across 43 modules), but for `npx`-install buyers the 49 Firebase-gated tools (workflow builder, funnel builder, form builder, pipeline builder, smart lists, reputation, email campaigns, memberships, email-builder internal, plus the pre-deploy validator) now actually register after `enable_workflow_builder` — previously they stayed "Not configured" no matter how many times you restarted.**
42
+
43
+ Reported independently by Henry Boulton and Ryan Thomas, 2026-05-26.
44
+
45
+ **Root cause.** Index startup folded `GHL_USER_ID` + the Firebase trio from `credentials.json` into `process.env`, but never folded `GHL_API_KEY` or `GHL_LOCATION_ID`. `WorkflowBuilderClient.fromEnv()` reads all five directly from `process.env`, so for the recommended `npx -y @elitedcs/ghl-mcp@latest` install path — where no env vars are set anywhere — `fromEnv()` returned `null` and every Firebase-gated tool was skipped. Wrapper-script installs (e.g. `start-mcp.sh`) were unaffected because they exported all the vars manually. Henry's workaround of passing the values as `--env` flags on `claude mcp add` "fixed" it by route, which is why this looked like a credentials-file persistence bug.
46
+
47
+ **Fix.** All credentials-file values now fold into `process.env` at startup (env still wins when already set, so wrapper-script + headless paths are unchanged). Pulled the fold into a single `foldCredentialsIntoEnv()` helper with regression tests that lock in all five Firebase-gated fields.
48
+
49
+ Also in this release:
50
+
51
+ - **`create_form`** now uses the correct path. GHL silently retired `POST /forms?locationId=…` (returns 404); the working path is `POST /forms/?locationId=…` (trailing slash). Verified against MCP Testing.
52
+ - **`update_form`** is honestly marked unavailable, with the actual root cause documented for the first time: the GHL form builder runs in a cross-origin iframe (`leadgen-apps-form-survey-builder.leadconnectorhq.com`) and writes saves **directly to Firestore via the Firebase JS SDK** — `firestore.googleapis.com/.../databases/(default)` against the `highlevel-backend` project — rather than any REST endpoint. That's why every REST probe returns 404 or "Form does not exist or is deleted." Wiring this up needs a Firestore-client integration (separate from the workflow-builder's REST-over-Firebase-token pattern). Tracked for a focused future release. Workaround: `delete_form` + `create_form` (both work).
53
+ - **`update_calendar`** removed `teamMembers` from the input. GHL's public API silently drops the field on PUT (200 OK, nothing persists) across every shape and API version we tested. The tool description now tells the caller to set the assignee in the GHL UI instead of returning a fake success.
54
+
55
+ ## 3.20.0 — License gate for headless / env-var installs
56
+
57
+ **Security + headless support. No tool changes (212 across 43 modules).**
58
+
59
+ Closes a gap where setting `GHL_API_KEY` + `GHL_LOCATION_ID` via environment variables loaded the full tool set **without ever validating a license**. The server now requires a verified license before exposing the full tools:
60
+
61
+ - A `credentials.json` written by `setup_ghl_mcp` carries `verified_at` (validated at setup) and is trusted as before — **no change for normal Desktop/Claude Code installs.**
62
+ - The env-var / headless path must now supply `GHL_LICENSE_EMAIL` + `GHL_LICENSE_KEY`. These are validated once at boot and the verified result is cached to `credentials.json`, so there's no per-restart phone-home and device activations aren't re-counted on a stable machine.
63
+ - Without a verified license, the server stays in bootstrap mode (only `setup_ghl_mcp`, `request_license`, `get_mcp_version`) with a clear message.
64
+
65
+ This makes headless/server installs first-class: set `GHL_LICENSE_EMAIL`, `GHL_LICENSE_KEY`, `GHL_API_KEY`, `GHL_LOCATION_ID` (plus the Firebase vars for the workflow builder) and the server boots into the full tool set.
66
+
3
67
  ## 3.19.1 — MCP registry prep + README UTM tracking
4
68
 
5
69
  - Added `mcpName` (`io.github.drjerryrelth/ghl-command`) to package.json so the package can be published to the official MCP registry (many directories pull from it).
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.19.1",
34
+ version: "3.23.0",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
36
  description: "GoHighLevel MCP Server for Claude. 212 tools \u2014 full CRM, automation, marketing control, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
37
37
  main: "dist/index.js",
@@ -60,9 +60,15 @@ var require_package = __commonJS({
60
60
  "model-context-protocol",
61
61
  "gohighlevel",
62
62
  "ghl",
63
+ "gohighlevel-mcp",
64
+ "ghl-automation",
63
65
  "crm",
64
66
  "claude",
65
- "anthropic"
67
+ "anthropic",
68
+ "workflow-builder",
69
+ "marketing-automation",
70
+ "ai-agent",
71
+ "agency"
66
72
  ],
67
73
  author: "Elite DCs, LLC",
68
74
  license: "MIT",
@@ -326,6 +332,23 @@ function readCredentials() {
326
332
  return null;
327
333
  }
328
334
  }
335
+ function foldCredentialsIntoEnv(creds, env = process.env) {
336
+ if (!creds) return;
337
+ const mappings = [
338
+ ["ghl_api_key", "GHL_API_KEY"],
339
+ ["ghl_location_id", "GHL_LOCATION_ID"],
340
+ ["ghl_user_id", "GHL_USER_ID"],
341
+ ["ghl_firebase_api_key", "GHL_FIREBASE_API_KEY"],
342
+ ["ghl_firebase_refresh_token", "GHL_FIREBASE_REFRESH_TOKEN"],
343
+ ["ghl_company_id", "GHL_COMPANY_ID"]
344
+ ];
345
+ for (const [credKey, envKey] of mappings) {
346
+ const value = creds[credKey];
347
+ if (value && !env[envKey]) {
348
+ env[envKey] = value;
349
+ }
350
+ }
351
+ }
329
352
  function writeCredentials(creds) {
330
353
  ensureAppDataDir();
331
354
  const file = credentialsPath();
@@ -2364,6 +2387,18 @@ function registerOpportunityTools(server2, client) {
2364
2387
 
2365
2388
  // src/tools/calendars.ts
2366
2389
  var import_zod9 = require("zod");
2390
+ var TeamMemberLocationConfigSchema = import_zod9.z.object({
2391
+ kind: import_zod9.z.string().describe("Meeting location kind. Common values: 'custom', 'zoom_conference', 'google_meet'."),
2392
+ position: import_zod9.z.number().int().nonnegative().describe("Position index for ordering (0-based)."),
2393
+ location: import_zod9.z.string().optional().describe("Display string for the meeting location (URL or address). Optional for kind='custom'.")
2394
+ });
2395
+ var CalendarTeamMemberSchema = import_zod9.z.object({
2396
+ userId: import_zod9.z.string().describe("GHL user ID to assign. Must exist in this sub-account."),
2397
+ priority: import_zod9.z.union([import_zod9.z.literal(0), import_zod9.z.literal(0.5), import_zod9.z.literal(1)]).describe("Booking priority. Exactly 0, 0.5, or 1 (anything else 422s from GHL). Higher = preferred for round-robin."),
2398
+ isPrimary: import_zod9.z.boolean().describe("Whether this user is the primary assignee."),
2399
+ selected: import_zod9.z.boolean().describe("Whether this user is currently active on the calendar (true to enable)."),
2400
+ locationConfigurations: import_zod9.z.array(TeamMemberLocationConfigSchema).min(1).describe("At least one meeting-location config. Typical shape: [{kind:'custom', position:0, location:''}].")
2401
+ });
2367
2402
  function registerCalendarTools(server2, client) {
2368
2403
  safeTool(
2369
2404
  server2,
@@ -2433,7 +2468,7 @@ function registerCalendarTools(server2, client) {
2433
2468
  safeTool(
2434
2469
  server2,
2435
2470
  "update_calendar",
2436
- "Update an existing calendar",
2471
+ "Update an existing calendar's settings. Supports name, description, slug, event title/color, slot durations, capacity, AND team-member assignment via `teamMembers`. Team members only apply to calendar types that use them (round_robin, class_booking, etc.) \u2014 on event calendars GHL accepts the call but silently ignores the field. priority MUST be 0, 0.5, or 1.",
2437
2472
  {
2438
2473
  calendarId: import_zod9.z.string().describe("The calendar ID to update"),
2439
2474
  name: import_zod9.z.string().optional().describe("Calendar name"),
@@ -2441,7 +2476,7 @@ function registerCalendarTools(server2, client) {
2441
2476
  slug: import_zod9.z.string().optional().describe("Calendar slug"),
2442
2477
  widgetSlug: import_zod9.z.string().optional().describe("Widget slug for embedding"),
2443
2478
  calendarType: import_zod9.z.string().optional().describe("Calendar type"),
2444
- teamMembers: import_zod9.z.array(import_zod9.z.record(import_zod9.z.unknown())).optional().describe("Array of team member objects"),
2479
+ teamMembers: import_zod9.z.array(CalendarTeamMemberSchema).optional().describe("Replace the calendar's team-member assignments. Pass [] to clear (round-robin calendars require at least one). Use get_calendar to inspect the current shape first."),
2445
2480
  eventTitle: import_zod9.z.string().optional().describe("Default event title"),
2446
2481
  eventColor: import_zod9.z.string().optional().describe("Event color hex code"),
2447
2482
  slotDuration: import_zod9.z.number().optional().describe("Slot duration in minutes"),
@@ -5476,6 +5511,12 @@ ${text2}`);
5476
5511
 
5477
5512
  // src/tools/form-builder.ts
5478
5513
  var import_zod35 = require("zod");
5514
+ function buildUpdateFormPath(formId, locationId2) {
5515
+ return `/${formId}?locationId=${locationId2}`;
5516
+ }
5517
+ function buildUpdateFormBody(name, formData) {
5518
+ return { name, formData };
5519
+ }
5479
5520
  function registerFormBuilderTools(server2, builderClient) {
5480
5521
  const client = builderClient;
5481
5522
  if (!client) return;
@@ -5519,17 +5560,28 @@ ${text2}`);
5519
5560
  );
5520
5561
  server2.tool(
5521
5562
  "update_form",
5522
- "Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to see the current structure, modify the formData object, and pass it here.",
5563
+ "Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to read the current form, modify the formData object, then pass it here. If name is omitted, the current name is preserved (one extra GET).",
5523
5564
  {
5524
5565
  formId: import_zod35.z.string().describe("The form ID to update."),
5525
5566
  formData: import_zod35.z.record(import_zod35.z.unknown()).describe("The updated formData object containing form fields, settings, autoResponder config, etc."),
5526
- name: import_zod35.z.string().optional().describe("Updated form name.")
5567
+ name: import_zod35.z.string().optional().describe("Updated form name. If omitted, the current name is preserved.")
5527
5568
  },
5528
5569
  async ({ formId, formData, name }) => {
5529
5570
  try {
5530
- const body = { formData, locationId: client.locationId };
5531
- if (name !== void 0) body.name = name;
5532
- const result = await formRequest("PUT", `/${formId}?locationId=${client.locationId}`, body);
5571
+ let resolvedName = name;
5572
+ if (resolvedName === void 0) {
5573
+ const current = await formRequest("GET", `/${formId}?locationId=${client.locationId}`);
5574
+ const currentName = typeof current === "object" && current !== null && "form" in current ? current.form?.name : void 0;
5575
+ if (typeof currentName !== "string") {
5576
+ throw new Error(`Could not resolve current form name for ${formId} (GET /forms/${formId} returned no string name).`);
5577
+ }
5578
+ resolvedName = currentName;
5579
+ }
5580
+ const result = await formRequest(
5581
+ "POST",
5582
+ buildUpdateFormPath(formId, client.locationId),
5583
+ buildUpdateFormBody(resolvedName, formData)
5584
+ );
5533
5585
  return {
5534
5586
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
5535
5587
  };
@@ -5546,7 +5598,7 @@ ${text2}`);
5546
5598
  },
5547
5599
  async ({ name }) => {
5548
5600
  try {
5549
- const result = await formRequest("POST", `?locationId=${client.locationId}`, {
5601
+ const result = await formRequest("POST", `/?locationId=${client.locationId}`, {
5550
5602
  name,
5551
5603
  locationId: client.locationId,
5552
5604
  formData: {
@@ -8297,18 +8349,7 @@ var registry = new TokenRegistry();
8297
8349
  var fileCreds = readCredentials();
8298
8350
  var locationId = process.env.GHL_LOCATION_ID || fileCreds?.ghl_location_id || void 0;
8299
8351
  var apiKey = process.env.GHL_API_KEY || fileCreds?.ghl_api_key || void 0;
8300
- if (fileCreds?.ghl_user_id && !process.env.GHL_USER_ID) {
8301
- process.env.GHL_USER_ID = fileCreds.ghl_user_id;
8302
- }
8303
- if (fileCreds?.ghl_firebase_api_key && !process.env.GHL_FIREBASE_API_KEY) {
8304
- process.env.GHL_FIREBASE_API_KEY = fileCreds.ghl_firebase_api_key;
8305
- }
8306
- if (fileCreds?.ghl_firebase_refresh_token && !process.env.GHL_FIREBASE_REFRESH_TOKEN) {
8307
- process.env.GHL_FIREBASE_REFRESH_TOKEN = fileCreds.ghl_firebase_refresh_token;
8308
- }
8309
- if (fileCreds?.ghl_company_id && !process.env.GHL_COMPANY_ID) {
8310
- process.env.GHL_COMPANY_ID = fileCreds.ghl_company_id;
8311
- }
8352
+ foldCredentialsIntoEnv(fileCreds);
8312
8353
  if (locationId && registry.hasTokens()) {
8313
8354
  const token = registry.getToken(locationId);
8314
8355
  if (token) {
@@ -8321,24 +8362,60 @@ var server = new import_mcp.McpServer({
8321
8362
  name: "elite-dcs-ghl",
8322
8363
  version: pkg.version
8323
8364
  });
8324
- var inBootstrapMode = !apiKey || !locationId;
8325
8365
  registerMetaTools(server, pkg.version);
8326
- if (inBootstrapMode) {
8327
- process.stderr.write(
8328
- `[ghl-mcp] Bootstrap mode: no credentials found.
8329
- [ghl-mcp] Looked for env vars (GHL_API_KEY, GHL_LOCATION_ID) and credentials file (${credentialsPath()}).
8366
+ var inBootstrapMode = true;
8367
+ async function resolveAccessAndRegister() {
8368
+ let licenseVerified = Boolean(fileCreds?.verified_at && fileCreds?.license_key);
8369
+ if (!licenseVerified && apiKey && locationId) {
8370
+ const licEmail = (process.env.GHL_LICENSE_EMAIL || fileCreds?.email)?.trim();
8371
+ const licKey = (process.env.GHL_LICENSE_KEY || fileCreds?.license_key)?.trim();
8372
+ if (licEmail && licKey) {
8373
+ const lic = await validateLicense(licEmail, licKey);
8374
+ if (lic.ok) {
8375
+ licenseVerified = true;
8376
+ try {
8377
+ writeCredentials({
8378
+ license_key: licKey,
8379
+ email: licEmail,
8380
+ verified_at: (/* @__PURE__ */ new Date()).toISOString(),
8381
+ ghl_api_key: apiKey,
8382
+ ghl_location_id: locationId,
8383
+ ...process.env.GHL_COMPANY_ID ? { ghl_company_id: process.env.GHL_COMPANY_ID } : {},
8384
+ ...process.env.GHL_USER_ID ? { ghl_user_id: process.env.GHL_USER_ID } : {},
8385
+ ...process.env.GHL_FIREBASE_API_KEY ? { ghl_firebase_api_key: process.env.GHL_FIREBASE_API_KEY } : {},
8386
+ ...process.env.GHL_FIREBASE_REFRESH_TOKEN ? { ghl_firebase_refresh_token: process.env.GHL_FIREBASE_REFRESH_TOKEN } : {}
8387
+ });
8388
+ process.stderr.write("[ghl-mcp] License validated and cached.\n");
8389
+ } catch {
8390
+ }
8391
+ } else {
8392
+ process.stderr.write(`[ghl-mcp] License check failed: ${lic.error}
8393
+ `);
8394
+ }
8395
+ } else {
8396
+ process.stderr.write(
8397
+ "[ghl-mcp] No verified license. For a headless/server install set GHL_LICENSE_EMAIL + GHL_LICENSE_KEY alongside GHL_API_KEY + GHL_LOCATION_ID; otherwise run setup_ghl_mcp.\n"
8398
+ );
8399
+ }
8400
+ }
8401
+ inBootstrapMode = !apiKey || !locationId || !licenseVerified;
8402
+ if (inBootstrapMode) {
8403
+ process.stderr.write(
8404
+ `[ghl-mcp] Bootstrap mode.
8405
+ [ghl-mcp] Need a valid license plus GHL_API_KEY + GHL_LOCATION_ID (env vars or credentials file at ${credentialsPath()}).
8330
8406
  [ghl-mcp] Only setup_ghl_mcp, request_license, and get_mcp_version are available. Run setup_ghl_mcp with your license + GHL credentials \u2014 or request_license if you don't have a license yet.
8331
8407
  `
8332
- );
8333
- registerSetupTool(server);
8334
- registerLeadCaptureTool(server);
8335
- } else {
8336
- const client = new GHLClient({ apiKey, locationId });
8337
- registerAllTools(server, client, registry, pkg.version);
8338
- registerEnableWorkflowBuilderTool(server);
8339
- if (fileCreds && !process.env.GHL_API_KEY) {
8340
- process.stderr.write(`[ghl-mcp] Loaded credentials from ${credentialsPath()}
8408
+ );
8409
+ registerSetupTool(server);
8410
+ registerLeadCaptureTool(server);
8411
+ } else {
8412
+ const client = new GHLClient({ apiKey, locationId });
8413
+ registerAllTools(server, client, registry, pkg.version);
8414
+ registerEnableWorkflowBuilderTool(server);
8415
+ if (fileCreds && !process.env.GHL_API_KEY) {
8416
+ process.stderr.write(`[ghl-mcp] Loaded credentials from ${credentialsPath()}
8341
8417
  `);
8418
+ }
8342
8419
  }
8343
8420
  }
8344
8421
  async function validateApiKey() {
@@ -8375,6 +8452,7 @@ async function checkForUpdates() {
8375
8452
  }
8376
8453
  }
8377
8454
  async function main() {
8455
+ await resolveAccessAndRegister();
8378
8456
  const transport = new import_stdio.StdioServerTransport();
8379
8457
  await server.connect(transport);
8380
8458
  if (inBootstrapMode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.19.1",
3
+ "version": "3.23.0",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 212 tools — full CRM, automation, marketing control, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
6
6
  "main": "dist/index.js",
@@ -29,9 +29,15 @@
29
29
  "model-context-protocol",
30
30
  "gohighlevel",
31
31
  "ghl",
32
+ "gohighlevel-mcp",
33
+ "ghl-automation",
32
34
  "crm",
33
35
  "claude",
34
- "anthropic"
36
+ "anthropic",
37
+ "workflow-builder",
38
+ "marketing-automation",
39
+ "ai-agent",
40
+ "agency"
35
41
  ],
36
42
  "author": "Elite DCs, LLC",
37
43
  "license": "MIT",