conduyt-mcp 4.68.0 → 4.70.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.
@@ -24,9 +24,51 @@ const pageFields = {
24
24
  leadSource: z.string().max(100).nullable().optional().describe("lead source stamped on contacts the page creates"),
25
25
  brandName: z.string().max(100).nullable().optional().describe("white-label name shown on the page and in Zoom meeting names"),
26
26
  embedAllowedOrigins: z.array(z.string().url()).nullable().optional().describe("origins allowed to embed/call the page; null or empty = any"),
27
+ documentsEnabled: z.boolean().optional().describe("the documents step: the public upload-url and documents routes answer for this page's bookings (bank statements and funding agreements attached to the lead)"),
28
+ documentsPageUrl: z.string().url().nullable().optional().describe("the site page the confirmation email links the visitor back to for the upload (https; the page reads ?appointment=<id>); null = no link"),
27
29
  availabilityRules: z.array(availabilityWindow).nullable().optional().describe("availability override; null = the host's own rules"),
28
30
  isActive: z.boolean().optional(),
29
31
  };
32
+ /** (Codex rb-r9 m3) every other field PATCH /booking-pages/{id} accepts (the OpenAPI BookingPagePatch schema): with pageFields
33
+ * the update tool covers the route's whole body, typed as the route normalises it */
34
+ const updateOnlyFields = {
35
+ durationOptions: z.array(z.object({ label: z.string().min(1), minutes: z.number().int().min(5).max(480) })).nullable().optional().describe("selectable durations; empty or null clears"),
36
+ bufferBefore: z.number().int().min(0).nullable().optional().describe("minutes blocked before each booking"),
37
+ bufferAfter: z.number().int().min(0).nullable().optional().describe("minutes blocked after each booking"),
38
+ maxBookingsPerDay: z.number().int().min(1).nullable().optional(),
39
+ color: z.string().regex(/^#[0-9a-fA-F]{3,8}$/).nullable().optional().describe("hex colour"),
40
+ thankYouMessage: z.string().max(2000).nullable().optional(),
41
+ redirectUrl: z.string().url().nullable().optional().describe("where the visitor goes after booking"),
42
+ requirePhone: z.boolean().optional(),
43
+ embedEnabled: z.boolean().optional(),
44
+ // (Codex rb-r10 m3) the shapes the route's sanitizers keep (an entry outside them is dropped; a list with none clears)
45
+ customQuestions: z.array(z.object({ id: z.string().max(57).optional(), label: z.string().min(1).max(500), type: z.enum(["short_text", "long_text", "email", "phone", "select", "checkbox", "date", "number", "hidden"]), required: z.boolean().optional(), options: z.array(z.string().max(500)).max(50).optional(), defaultValue: z.string().max(500).optional() })).max(50).optional(),
46
+ formFields: z.array(z.object({ id: z.string().min(1).max(64), label: z.string().min(1).max(120), type: z.enum(["text", "email", "tel", "textarea"]), show: z.boolean(), required: z.boolean(), locked: z.boolean().optional() })).max(20).nullable().optional(),
47
+ fieldMappings: z.array(z.object({ formFieldId: z.string().min(1).max(64), contactField: z.string().regex(/^(firstName|lastName|email|phone|company|jobTitle|address|city|state|zip|country|source|notes|website|customFields\.[a-zA-Z_][a-zA-Z0-9_]*)$/) })).max(20).nullable().optional(),
48
+ reminders: z.array(z.object({ type: z.enum(["email", "sms"]), minutesBefore: z.number().int().min(1).max(10080) })).max(10).optional(),
49
+ logoUrl: z.string().url().nullable().optional(),
50
+ hideBranding: z.boolean().optional(),
51
+ template: z.enum(["bda"]).nullable().optional().describe("null = the default page, 'bda' = the BDA template"),
52
+ reviews: z.object({ badges: z.array(z.record(z.string(), z.unknown())).optional(), testimonials: z.array(z.record(z.string(), z.unknown())).optional() }).nullable().optional(),
53
+ campaign: z.string().nullable().optional(),
54
+ hyrosTag: z.string().nullable().optional(),
55
+ eventType: z.enum(["one_on_one", "round_robin", "collective", "group"]).optional(),
56
+ maxAttendees: z.number().int().min(1).nullable().optional(),
57
+ hostUserIds: z.array(z.string().uuid()).optional().describe("users on this account who can host"),
58
+ hostGroupId: z.string().uuid().nullable().optional(),
59
+ hostPriorities: z.record(z.string(), z.enum(["highest", "high", "low", "lowest"])).nullable().optional().describe("host user id to priority"),
60
+ distributionMethod: z.enum(["maximize", "equal"]).optional(),
61
+ paymentEnabled: z.boolean().optional().describe("accepted, but checkout is not implemented yet: the page stays unpaid and the answer carries a warning"),
62
+ paymentAmount: z.number().int().min(1).nullable().optional().describe("cents"),
63
+ paymentCurrency: z.enum(["usd", "eur", "gbp", "cad", "aud", "mxn"]).nullable().optional(),
64
+ stripeProductId: z.string().nullable().optional(),
65
+ stripePriceId: z.string().nullable().optional(),
66
+ gdprEnabled: z.boolean().optional(),
67
+ gdprText: z.string().max(2000).nullable().optional(),
68
+ gdprLinkUrl: z.string().url().nullable().optional(),
69
+ waitlistEnabled: z.boolean().optional(),
70
+ enableIcalFeed: z.boolean().optional().describe("true issues a new iCal feed token, false revokes it"),
71
+ };
30
72
  export function registerBookingPageTools(server, client) {
31
73
  server.tool("conduyt_list_booking_pages", "List the account's booking pages (GET /api/v1/booking-pages): slug, title, host, duration, active, video option. Requires calendar:view.", { activeOnly: z.boolean().optional().describe("only pages accepting bookings") }, async ({ activeOnly }) => {
32
74
  const result = await client.get(`/api/v1/booking-pages?${activeOnly ? "is_active=true" : ""}`);
@@ -40,7 +82,7 @@ export function registerBookingPageTools(server, client) {
40
82
  const result = await client.post("/api/v1/booking-pages", params);
41
83
  return formatResult(result);
42
84
  });
43
- server.tool("conduyt_update_booking_page", "Update a booking page (PATCH /api/v1/booking-pages/{id}): only the fields given change. videoProvider 'zoom' turns on one Zoom meeting per booking (null turns it off and clears the host); videoHostEmail must be a user of the connected Zoom account (null = the app's own user). Requires calendar:edit (admin/owner).", { id: z.string().uuid().describe("booking page UUID"), ...pageFields }, async ({ id, ...updates }) => {
85
+ server.tool("conduyt_update_booking_page", "Update a booking page (PATCH /api/v1/booking-pages/{id}): only the fields given change. videoProvider 'zoom' turns on one Zoom meeting per booking (null turns it off and clears the host); videoHostEmail must be a user of the connected Zoom account (null = the app's own user). Requires calendar:edit (admin/owner).", { id: z.string().uuid().describe("booking page UUID"), ...pageFields, ...updateOnlyFields }, async ({ id, ...updates }) => {
44
86
  const result = await client.patch(`/api/v1/booking-pages/${id}`, updates);
45
87
  return formatResult(result);
46
88
  });
@@ -95,4 +95,18 @@ export function registerDripCampaignTools(server, client) {
95
95
  const result = await client.post("/api/v1/drip-enrollments/batch/stop", params);
96
96
  return formatResult(result);
97
97
  });
98
+ // (Smart Views slice A, Codex r70 m5) the whole enrollment's barrier at once, including a MARKER-ONLY barrier (the
99
+ // processor died between its marker and its log: no webhook log exists to cancel)
100
+ server.tool("conduyt_cancel_drip_enrollment_barrier", "Cancel a drip enrollment's whole send barrier (POST /api/v1/drip-enrollments/{id}/cancel-barrier; admin or owner, the drip-enrollments scope): every parked / queued / failed webhook log of the enrollment is cancelled, including the case where no webhook log exists at all (the processor died after writing its marker). Without a resolution the barrier STANDS (nothing is sent). resolution 'skip' assumes the current step was delivered: the processor advances without sending. resolution 'resend' makes the processor rebuild and send the step AGAIN (a possible duplicate SMS): refused unless acknowledgeDuplicateRisk is true. Answers 404 (no such enrollment), 409 drip_send_in_flight (a request is in flight: retry in a minute) or 409 step_already_sent (the step already carries sent evidence: a resend would only advance it, use 'skip'). The response carries resolution, resendScheduled, advanceScheduled, logsCancelled, markersResolved, handedBack and barrierStanding.", {
101
+ id: z.string().uuid().describe("Drip enrollment UUID"),
102
+ resolution: z.enum(["resend", "skip"]).optional().describe("Omit to cancel only (the barrier stands). 'skip' = assume delivered, advance. 'resend' = send the step again (duplicate risk: needs acknowledgeDuplicateRisk)"),
103
+ acknowledgeDuplicateRisk: z.boolean().optional().describe("Required true with resolution 'resend': you accept the step may be delivered twice"),
104
+ }, async ({ id, resolution, acknowledgeDuplicateRisk }) => {
105
+ if (resolution === "resend" && acknowledgeDuplicateRisk !== true) {
106
+ return formatResult({ error: "resolution 'resend' may deliver the step twice: pass acknowledgeDuplicateRisk: true, or use resolution 'skip' to advance without sending" });
107
+ }
108
+ // the body as ONE literal: the contract gate reads the request fields from it (resolution, confirm)
109
+ const result = await client.post(`/api/v1/drip-enrollments/${id}/cancel-barrier`, { ...(resolution ? { resolution } : {}), ...(resolution === "resend" ? { confirm: "RESEND_UNCONFIRMED_DRIP" } : {}) });
110
+ return formatResult(result);
111
+ });
98
112
  }
@@ -38,4 +38,17 @@ export function registerWebhookTools(server, client) {
38
38
  const result = await client.del(`/api/v1/webhooks/manage/${id}`);
39
39
  return formatResult(result);
40
40
  });
41
+ // (Smart Views slice A, Codex r70 m5) the drip send barrier's operator recovery, first-class for agents
42
+ server.tool("conduyt_cancel_webhook_log", "Cancel ONE parked / queued / failed webhook log of a drip send whose outcome was unconfirmed (POST /api/v1/webhook-logs/{id}/cancel; admin or owner, the webhooks scope). Without a resolution the log is cancelled and the barrier STANDS (nothing is sent, the enrollment stays parked). resolution 'skip' assumes the step was delivered: the processor advances without sending. resolution 'resend' makes the processor rebuild and send the step AGAIN (a possible duplicate SMS): it is refused unless acknowledgeDuplicateRisk is true. Answers 404 (no such log), 409 webhook_log_in_flight (a request is in flight right now: retry in a minute), 409 webhook_log_not_cancellable (already cancelled / succeeded) or 409 step_already_sent (the step already carries sent evidence: a resend would only advance it, use 'skip'). The response carries resolution, resendScheduled, advanceScheduled, handedBack, markersResolved and barrierStanding.", {
43
+ id: z.string().uuid().describe("Webhook log UUID (from the replay answer's replayBlockedIds / replayUnarmableIds, or the merge's 409 drip_send_unconfirmed)"),
44
+ resolution: z.enum(["resend", "skip"]).optional().describe("Omit to cancel only (the barrier stands). 'skip' = assume delivered, advance. 'resend' = send the step again (duplicate risk: needs acknowledgeDuplicateRisk)"),
45
+ acknowledgeDuplicateRisk: z.boolean().optional().describe("Required true with resolution 'resend': you accept the step may be delivered twice"),
46
+ }, async ({ id, resolution, acknowledgeDuplicateRisk }) => {
47
+ if (resolution === "resend" && acknowledgeDuplicateRisk !== true) {
48
+ return formatResult({ error: "resolution 'resend' may deliver the step twice: pass acknowledgeDuplicateRisk: true, or use resolution 'skip' to advance without sending" });
49
+ }
50
+ // the body as ONE literal: the contract gate reads the request fields from it (resolution, confirm)
51
+ const result = await client.post(`/api/v1/webhook-logs/${id}/cancel`, { ...(resolution ? { resolution } : {}), ...(resolution === "resend" ? { confirm: "RESEND_UNCONFIRMED_DRIP" } : {}) });
52
+ return formatResult(result);
53
+ });
41
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.68.0",
3
+ "version": "4.70.0",
4
4
  "description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,11 +1,11 @@
1
1
  {
2
- "generatedAt": "2026-09-26T07:26:50.579Z",
3
- "toolCount": 288,
4
- "totalEndpoints": 1008,
2
+ "generatedAt": "2026-09-27T22:23:01.252Z",
3
+ "toolCount": 290,
4
+ "totalEndpoints": 1015,
5
5
  "counts": {
6
- "covered": 264,
7
- "excluded": 295,
8
- "planned": 449,
6
+ "covered": 266,
7
+ "excluded": 299,
8
+ "planned": 450,
9
9
  "uncategorized": 0
10
10
  },
11
11
  "matrix": {
@@ -2499,6 +2499,12 @@
2499
2499
  "conduyt_list_drip_enrollments"
2500
2500
  ]
2501
2501
  },
2502
+ "POST /api/v1/drip-enrollments/:id/cancel-barrier": {
2503
+ "status": "covered",
2504
+ "tools": [
2505
+ "conduyt_cancel_drip_enrollment_barrier"
2506
+ ]
2507
+ },
2502
2508
  "GET /api/v1/drip-enrollments/:id/timeline": {
2503
2509
  "status": "planned",
2504
2510
  "reason": "drip enrollment timeline inspection"
@@ -3233,6 +3239,14 @@
3233
3239
  "status": "excluded",
3234
3240
  "reason": "public unauthenticated surfaces — no key semantics"
3235
3241
  },
3242
+ "POST /api/v1/public/booking/:slug/documents": {
3243
+ "status": "excluded",
3244
+ "reason": "public unauthenticated surfaces — no key semantics"
3245
+ },
3246
+ "OPTIONS /api/v1/public/booking/:slug/documents": {
3247
+ "status": "excluded",
3248
+ "reason": "public unauthenticated surfaces — no key semantics"
3249
+ },
3236
3250
  "GET /api/v1/public/booking/:slug/embed": {
3237
3251
  "status": "excluded",
3238
3252
  "reason": "public unauthenticated surfaces — no key semantics"
@@ -3261,6 +3275,14 @@
3261
3275
  "status": "excluded",
3262
3276
  "reason": "public unauthenticated surfaces — no key semantics"
3263
3277
  },
3278
+ "POST /api/v1/public/booking/:slug/upload-url": {
3279
+ "status": "excluded",
3280
+ "reason": "public unauthenticated surfaces — no key semantics"
3281
+ },
3282
+ "OPTIONS /api/v1/public/booking/:slug/upload-url": {
3283
+ "status": "excluded",
3284
+ "reason": "public unauthenticated surfaces — no key semantics"
3285
+ },
3264
3286
  "POST /api/v1/public/booking/:slug/waitlist": {
3265
3287
  "status": "excluded",
3266
3288
  "reason": "public unauthenticated surfaces — no key semantics"
@@ -4061,6 +4083,10 @@
4061
4083
  "conduyt_move_smart_view"
4062
4084
  ]
4063
4085
  },
4086
+ "GET /api/v1/smart-views/version": {
4087
+ "status": "planned",
4088
+ "reason": "smart view management beyond summaries"
4089
+ },
4064
4090
  "GET /api/v1/sms-providers": {
4065
4091
  "status": "planned",
4066
4092
  "reason": "SMS surfaces beyond send tools"
@@ -4394,6 +4420,12 @@
4394
4420
  "status": "planned",
4395
4421
  "reason": "outbound webhook delivery logs"
4396
4422
  },
4423
+ "POST /api/v1/webhook-logs/:id/cancel": {
4424
+ "status": "covered",
4425
+ "tools": [
4426
+ "conduyt_cancel_webhook_log"
4427
+ ]
4428
+ },
4397
4429
  "GET /api/v1/webhooks": {
4398
4430
  "status": "planned",
4399
4431
  "reason": "outbound webhook subscription management (distinct from the inbound /webhooks/ receivers)"