conduyt-mcp 4.56.0 → 4.58.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/dist/index.js CHANGED
@@ -25,6 +25,7 @@ import { registerCustomFieldTools } from "./tools/custom-fields.js";
25
25
  import { registerSmartListTools } from "./tools/smart-lists.js";
26
26
  import { registerUserTools } from "./tools/users.js";
27
27
  import { registerBulkTools } from "./tools/bulk.js";
28
+ import { registerBatchOperationTools } from "./tools/batch-operations.js";
28
29
  import { registerDncTools } from "./tools/dnc.js";
29
30
  import { registerSavedFilterTools } from "./tools/saved-filters.js";
30
31
  import { registerDripCampaignTools } from "./tools/drip-campaigns.js";
@@ -43,6 +44,8 @@ import { registerCallFlowTools } from "./tools/call-flows.js";
43
44
  import { registerProjectBlueTools } from "./tools/project-blue.js";
44
45
  import { registerWhatsappTools } from "./tools/whatsapp.js";
45
46
  import { registerFromkTools } from "./tools/fromk.js";
47
+ import { registerZoomTools } from "./tools/zoom.js";
48
+ import { registerBookingPageTools } from "./tools/booking-pages.js";
46
49
  import { registerReportTools } from "./tools/reports.js";
47
50
  import { registerReportDashboardTools } from "./tools/dashboards.js";
48
51
  const apiUrl = process.env.CONDUYT_API_URL;
@@ -92,6 +95,7 @@ registerCustomFieldTools(server, client);
92
95
  registerSmartListTools(server, client);
93
96
  registerUserTools(server, client);
94
97
  registerBulkTools(server, client);
98
+ registerBatchOperationTools(server, client);
95
99
  registerDncTools(server, client);
96
100
  registerSavedFilterTools(server, client);
97
101
  registerDripCampaignTools(server, client);
@@ -110,6 +114,8 @@ registerCallFlowTools(server, client);
110
114
  registerProjectBlueTools(server, client);
111
115
  registerWhatsappTools(server, client);
112
116
  registerFromkTools(server, client);
117
+ registerZoomTools(server, client);
118
+ registerBookingPageTools(server, client);
113
119
  registerReportTools(server, client);
114
120
  registerReportDashboardTools(server, client);
115
121
  const transport = new StdioServerTransport();
@@ -0,0 +1,7 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ /**
4
+ * Run Batch (Conduyt #80, 2026-09-15): one logged task on many contacts — the contacts page's "Select all N
5
+ * matching → Run Batch" action and the Run Batch page. Admins and owners only.
6
+ */
7
+ export declare function registerBatchOperationTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ /**
4
+ * Run Batch (Conduyt #80, 2026-09-15): one logged task on many contacts — the contacts page's "Select all N
5
+ * matching → Run Batch" action and the Run Batch page. Admins and owners only.
6
+ */
7
+ export function registerBatchOperationTools(server, client) {
8
+ server.tool("conduyt_create_batch_operation", "Queue a Run Batch: ONE task applied to many contacts, logged with progress and cancel (no undo). Tasks: enroll (automationId of a published workflow), delete (the #73 engine: cleanup runs, contacts with an upcoming appointment are kept), add_tag / remove_tag (params.tagId), assign (params.userId — one rep) or a round-robin pool (params.userIds, 2–50 reps rotated in order; a rep who is no longer an active member is skipped), set_source (params.source — OVERWRITES the lead source), set_status (params.status — one of the account's lead statuses; a lead with a won deal keeps its status). Pick the contacts ONE way: contactIds (up to 50,000), filterPayload (universal filters), or filter + expectedTotal — the same filter you would give conduyt_list_contacts plus the meta.total it returned; the server re-counts and refuses with 409 expected_total_mismatch when the set moved, then freezes the matching ids (max 50,000). The run is processed in the background; poll conduyt_get_batch_operation. Requires the admin or owner role and an active subscription.", {
9
+ name: z.string().min(1).max(256).describe("A name for the run (shown on the Run Batch page)"),
10
+ operation: z.enum(["enroll", "delete", "add_tag", "remove_tag", "assign", "set_source", "set_status"]).describe("The task"),
11
+ automationId: z.string().uuid().optional().describe("enroll only: the published workflow"),
12
+ params: z.record(z.string(), z.unknown()).optional().describe("Task arguments: { tagId } | { userId } | { userIds: [...] } | { source } | { status }; none for enroll and delete"),
13
+ comment: z.string().max(2000).optional().describe("Why this run (stored with it)"),
14
+ contactIds: z.array(z.string().uuid()).min(1).max(50_000).optional().describe("Explicit contacts"),
15
+ filterPayload: z.array(z.record(z.string(), z.unknown())).min(1).optional().describe("Universal filter rules (the Smart View filter shape)"),
16
+ filter: z.record(z.string(), z.union([z.string(), z.array(z.string())])).optional().describe("The contact list filter parameters, exactly as for conduyt_list_contacts; at least one non-empty value"),
17
+ expectedTotal: z.number().int().positive().optional().describe("With filter: the total the list showed for it (meta.total)"),
18
+ }, async (params) => {
19
+ const body = { name: params.name, operation: params.operation };
20
+ if (params.automationId)
21
+ body.automationId = params.automationId;
22
+ if (params.params)
23
+ body.params = params.params;
24
+ if (params.comment)
25
+ body.comment = params.comment;
26
+ if (params.contactIds)
27
+ body.contactIds = params.contactIds;
28
+ else if (params.filterPayload)
29
+ body.filterPayload = params.filterPayload;
30
+ else {
31
+ body.filter = params.filter;
32
+ body.expectedTotal = params.expectedTotal;
33
+ }
34
+ const result = await client.post("/api/v1/batch-operations", body);
35
+ return formatResult(result);
36
+ });
37
+ server.tool("conduyt_list_batch_operations", "List Run Batch runs (newest first): task (operation + params with the display names), status (queued | running | completed | cancelled | failed), totals and progress, creator, comment.", {
38
+ status: z.enum(["queued", "running", "completed", "cancelled", "failed"]).optional().describe("Only runs in this status"),
39
+ createdBy: z.string().uuid().optional().describe("Only runs created by this user"),
40
+ page: z.number().int().positive().optional().describe("Page (default 1)"),
41
+ limit: z.number().int().positive().max(50).optional().describe("Rows per page (default 20, max 50)"),
42
+ }, async (params) => {
43
+ const qp = new URLSearchParams();
44
+ if (params.status)
45
+ qp.set("status", params.status);
46
+ if (params.createdBy)
47
+ qp.set("createdBy", params.createdBy);
48
+ if (params.page)
49
+ qp.set("page", String(params.page));
50
+ if (params.limit)
51
+ qp.set("limit", String(params.limit));
52
+ const qs = qp.toString();
53
+ const result = await client.get(`/api/v1/batch-operations${qs ? `?${qs}` : ""}`);
54
+ return formatResult(result);
55
+ });
56
+ server.tool("conduyt_get_batch_operation", "One Run Batch run with its per-contact items: status per contact (pending | success | error | skipped) with the reason (e.g. 'kept: upcoming appointment', 'tag already applied', 'a lead a deal has won keeps its status').", {
57
+ id: z.string().uuid().describe("The run id"),
58
+ itemStatus: z.enum(["all", "pending", "success", "error", "skipped"]).optional().describe("Only items in this status (default all)"),
59
+ page: z.number().int().positive().optional().describe("Item page (default 1)"),
60
+ limit: z.number().int().positive().max(50).optional().describe("Items per page (default 20, max 50)"),
61
+ }, async (params) => {
62
+ const qp = new URLSearchParams();
63
+ if (params.itemStatus)
64
+ qp.set("itemStatus", params.itemStatus);
65
+ if (params.page)
66
+ qp.set("page", String(params.page));
67
+ if (params.limit)
68
+ qp.set("limit", String(params.limit));
69
+ const qs = qp.toString();
70
+ const result = await client.get(`/api/v1/batch-operations/${params.id}${qs ? `?${qs}` : ""}`);
71
+ return formatResult(result);
72
+ });
73
+ server.tool("conduyt_cancel_batch_operation", "Stop a queued or running Run Batch. Contacts already processed stay as they are (there is no undo); claimed-but-unprocessed contacts go back to pending and are not touched.", {
74
+ id: z.string().uuid().describe("The run id"),
75
+ }, async (params) => {
76
+ const result = await client.post(`/api/v1/batch-operations/${params.id}/cancel`, {});
77
+ return formatResult(result);
78
+ });
79
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ export declare function registerBookingPageTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ /**
4
+ * Booking pages (Conduyt #72, 2026-09-15): the public scheduling pages. Until now an AI operator could read appointments
5
+ * but not the pages that create them; these tools cover the page itself and its video option (videoProvider "zoom" =
6
+ * one Zoom meeting per booking, see conduyt_zoom_connect).
7
+ */
8
+ const availabilityWindow = z.object({
9
+ dayOfWeek: z.number().int().min(0).max(6).describe("0 = Sunday … 6 = Saturday"),
10
+ startTime: z.string().regex(/^\d{2}:\d{2}$/).describe("HH:MM in the booking timezone"),
11
+ endTime: z.string().regex(/^\d{2}:\d{2}$/).describe("HH:MM in the booking timezone"),
12
+ });
13
+ const pageFields = {
14
+ title: z.string().min(1).max(200).optional().describe("page title"),
15
+ slug: z.string().min(1).max(100).optional().describe("public slug (conduyt.app/book/<slug>)"),
16
+ description: z.string().max(2000).nullable().optional(),
17
+ userId: z.string().uuid().optional().describe("host user UUID (the page owner)"),
18
+ durationMinutes: z.number().int().min(5).max(480).optional(),
19
+ minimumNotice: z.number().int().min(0).optional().describe("earliest booking, minutes from now"),
20
+ maxAdvanceDays: z.number().int().min(0).optional().describe("how far ahead visitors may book"),
21
+ location: z.string().max(500).nullable().optional().describe("written location (link, phone, address); with videoProvider zoom it is the fallback when Zoom is down"),
22
+ videoProvider: z.enum(["zoom"]).nullable().optional().describe("'zoom' = a new Zoom meeting for every booking (needs conduyt_zoom_connect); null = the written location only"),
23
+ videoHostEmail: z.string().email().nullable().optional().describe("the Zoom user the meeting is created under (a user of the connected Zoom account); null = the app's own user"),
24
+ leadSource: z.string().max(100).nullable().optional().describe("lead source stamped on contacts the page creates"),
25
+ brandName: z.string().max(100).nullable().optional().describe("white-label name shown on the page and in Zoom meeting names"),
26
+ embedAllowedOrigins: z.array(z.string().url()).nullable().optional().describe("origins allowed to embed/call the page; null or empty = any"),
27
+ availabilityRules: z.array(availabilityWindow).nullable().optional().describe("availability override; null = the host's own rules"),
28
+ isActive: z.boolean().optional(),
29
+ };
30
+ export function registerBookingPageTools(server, client) {
31
+ 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
+ const result = await client.get(`/api/v1/booking-pages?${activeOnly ? "is_active=true" : ""}`);
33
+ return formatResult(result);
34
+ });
35
+ server.tool("conduyt_get_booking_page", "One booking page with every setting (GET /api/v1/booking-pages/{id}). Requires calendar:view.", { id: z.string().uuid().describe("booking page UUID") }, async ({ id }) => {
36
+ const result = await client.get(`/api/v1/booking-pages/${id}`);
37
+ return formatResult(result);
38
+ });
39
+ server.tool("conduyt_create_booking_page", "Create a booking page (POST /api/v1/booking-pages): title, slug, userId (host) and durationMinutes at least; optional written location, videoProvider 'zoom' + videoHostEmail for one Zoom meeting per booking, availabilityRules override, embedAllowedOrigins, leadSource, brandName. Requires calendar:edit (admin/owner).", { ...pageFields, title: z.string().min(1).max(200), slug: z.string().min(1).max(100), userId: z.string().uuid(), durationMinutes: z.number().int().min(5).max(480) }, async (params) => {
40
+ const result = await client.post("/api/v1/booking-pages", params);
41
+ return formatResult(result);
42
+ });
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 }) => {
44
+ const result = await client.patch(`/api/v1/booking-pages/${id}`, updates);
45
+ return formatResult(result);
46
+ });
47
+ }
@@ -0,0 +1,9 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ /**
4
+ * Zoom (Conduyt #72, 2026-09-15): the tenant's Zoom Server-to-Server OAuth app as a Conduyt connection. Booking pages
5
+ * with videoProvider "zoom" create one Zoom meeting per booking (the join link is the appointment's location, rides the
6
+ * confirmation email and the host's calendar; cancellations delete it, reschedules move it). These tools cover what an
7
+ * AI operator needs: is it connected, connect/replace/disconnect the app, and prove it (plus a chosen host) works.
8
+ */
9
+ export declare function registerZoomTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ /**
4
+ * Zoom (Conduyt #72, 2026-09-15): the tenant's Zoom Server-to-Server OAuth app as a Conduyt connection. Booking pages
5
+ * with videoProvider "zoom" create one Zoom meeting per booking (the join link is the appointment's location, rides the
6
+ * confirmation email and the host's calendar; cancellations delete it, reschedules move it). These tools cover what an
7
+ * AI operator needs: is it connected, connect/replace/disconnect the app, and prove it (plus a chosen host) works.
8
+ */
9
+ export function registerZoomTools(server, client) {
10
+ server.tool("conduyt_zoom_status", "Zoom connection for this account (GET /api/v1/settings/zoom): status connected | not_configured, the Zoom account id and client id masked (the client secret is never returned). Requires settings:view.", {}, async () => {
11
+ const result = await client.get("/api/v1/settings/zoom");
12
+ return formatResult(result);
13
+ });
14
+ server.tool("conduyt_zoom_connect", "Connect or replace the account's Zoom Server-to-Server OAuth app (PUT /api/v1/settings/zoom): accountId, clientId, clientSecret from the Zoom App Marketplace app with scopes meeting:write/read/update/delete:meeting:admin + user:read:user:admin. Verified with Zoom before it is stored (a token must be issued and the app's own user readable: 422 credentials_rejected / scope_missing, 502 vendor_unavailable, nothing stored). Answers the app's user = the default meeting host. Pass disconnect: true to forget the app instead (DELETE): booking pages set to Zoom fall back to their written location. Requires settings:edit.", {
15
+ accountId: z.string().min(8).max(64).optional().describe("Zoom account id of the S2S app; required unless disconnect is true"),
16
+ clientId: z.string().min(8).max(64).optional().describe("Zoom client id; required unless disconnect is true"),
17
+ clientSecret: z.string().min(16).max(200).optional().describe("Zoom client secret; required unless disconnect is true"),
18
+ disconnect: z.boolean().optional().describe("true = forget the stored app (DELETE)"),
19
+ force: z.boolean().optional().describe("replace or disconnect although upcoming bookings still hold Zoom meetings the current app created (409 zoom_meetings_active otherwise); those meetings then stay on the old Zoom account and cancellations record zoom_orphaned"),
20
+ }, async ({ accountId, clientId, clientSecret, disconnect, force }) => {
21
+ if (disconnect) {
22
+ const result = await client.del(`/api/v1/settings/zoom?${force ? "force=1" : ""}`);
23
+ return formatResult(result);
24
+ }
25
+ if (!accountId || !clientId || !clientSecret)
26
+ throw new Error("accountId, clientId and clientSecret are required unless disconnect is true");
27
+ const result = await client.put("/api/v1/settings/zoom", { accountId, clientId, clientSecret, ...(force ? { force: true } : {}) });
28
+ return formatResult(result);
29
+ });
30
+ server.tool("conduyt_zoom_test", "Prove the stored Zoom app still works (POST /api/v1/settings/zoom/test): a token is issued and the app's own user answers; pass hostEmail to also prove that user belongs to the Zoom account (the value a booking page's videoHostEmail must be). Read-only on Zoom, nothing is created. 200 with ok:false + code host_not_found | credentials_rejected | scope_missing | vendor_unavailable when it does not; 409 zoom_not_configured before connect. Requires settings:view.", {
31
+ hostEmail: z.string().email().optional().describe("a Zoom user of the connected account to verify as a meeting host"),
32
+ }, async ({ hostEmail }) => {
33
+ const result = await client.post("/api/v1/settings/zoom/test", { hostEmail: hostEmail ?? undefined });
34
+ return formatResult(result);
35
+ });
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.56.0",
3
+ "version": "4.58.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-15T16:44:24.985Z",
3
- "toolCount": 264,
4
- "totalEndpoints": 979,
2
+ "generatedAt": "2026-09-16T02:16:31.092Z",
3
+ "toolCount": 275,
4
+ "totalEndpoints": 983,
5
5
  "counts": {
6
- "covered": 247,
6
+ "covered": 257,
7
7
  "excluded": 291,
8
- "planned": 441,
8
+ "planned": 435,
9
9
  "uncategorized": 0
10
10
  },
11
11
  "matrix": {
@@ -739,16 +739,20 @@
739
739
  "reason": "batch operation monitoring"
740
740
  },
741
741
  "POST /api/v1/batch-operations": {
742
- "status": "planned",
743
- "reason": "batch operation monitoring"
742
+ "status": "covered",
743
+ "tools": [
744
+ "conduyt_create_batch_operation"
745
+ ]
744
746
  },
745
747
  "GET /api/v1/batch-operations/:id": {
746
748
  "status": "planned",
747
749
  "reason": "batch operation monitoring"
748
750
  },
749
751
  "POST /api/v1/batch-operations/:id/cancel": {
750
- "status": "planned",
751
- "reason": "batch operation monitoring"
752
+ "status": "covered",
753
+ "tools": [
754
+ "conduyt_cancel_batch_operation"
755
+ ]
752
756
  },
753
757
  "GET /api/v1/billing/addons": {
754
758
  "status": "excluded",
@@ -787,20 +791,28 @@
787
791
  "reason": "the API itself rejects key-based billing management (interactive session required)"
788
792
  },
789
793
  "GET /api/v1/booking-pages": {
790
- "status": "planned",
791
- "reason": "booking page management"
794
+ "status": "covered",
795
+ "tools": [
796
+ "conduyt_list_booking_pages"
797
+ ]
792
798
  },
793
799
  "POST /api/v1/booking-pages": {
794
- "status": "planned",
795
- "reason": "booking page management"
800
+ "status": "covered",
801
+ "tools": [
802
+ "conduyt_create_booking_page"
803
+ ]
796
804
  },
797
805
  "GET /api/v1/booking-pages/:id": {
798
- "status": "planned",
799
- "reason": "booking page management"
806
+ "status": "covered",
807
+ "tools": [
808
+ "conduyt_get_booking_page"
809
+ ]
800
810
  },
801
811
  "PATCH /api/v1/booking-pages/:id": {
802
- "status": "planned",
803
- "reason": "booking page management"
812
+ "status": "covered",
813
+ "tools": [
814
+ "conduyt_update_booking_page"
815
+ ]
804
816
  },
805
817
  "DELETE /api/v1/booking-pages/:id": {
806
818
  "status": "planned",
@@ -3836,6 +3848,30 @@
3836
3848
  "conduyt_whatsapp_senders"
3837
3849
  ]
3838
3850
  },
3851
+ "GET /api/v1/settings/zoom": {
3852
+ "status": "covered",
3853
+ "tools": [
3854
+ "conduyt_zoom_status"
3855
+ ]
3856
+ },
3857
+ "PUT /api/v1/settings/zoom": {
3858
+ "status": "covered",
3859
+ "tools": [
3860
+ "conduyt_zoom_connect"
3861
+ ]
3862
+ },
3863
+ "DELETE /api/v1/settings/zoom": {
3864
+ "status": "covered",
3865
+ "tools": [
3866
+ "conduyt_zoom_connect"
3867
+ ]
3868
+ },
3869
+ "POST /api/v1/settings/zoom/test": {
3870
+ "status": "covered",
3871
+ "tools": [
3872
+ "conduyt_zoom_test"
3873
+ ]
3874
+ },
3839
3875
  "GET /api/v1/smart-lists": {
3840
3876
  "status": "covered",
3841
3877
  "tools": [