conduyt-mcp 4.66.0 → 4.67.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.
@@ -37,17 +37,20 @@ export function registerSavedFilterTools(server, client) {
37
37
  const result = await client.get(`/api/v1/saved-filters${qs ? `?${qs}` : ""}`);
38
38
  return formatResult(result);
39
39
  });
40
- server.tool("conduyt_create_saved_filter", "Save a named contact filter for one-click re-use — e.g. \"save a filter for Heather's Facebook leads\". Names are unique per user (409 on duplicate); max 50 saved filters per user. Requires the 'saved-filters' scope.", {
40
+ server.tool("conduyt_create_saved_filter", "Save a named contact filter for one-click re-use — e.g. \"save a filter for Heather's Facebook leads\". Since 2026-09-24 a saved filter IS a personal Smart View (the Smart Views tab), private to the key's user for every role (max 50 per user). Requires the 'saved-filters' scope.", {
41
41
  name: z.string().min(1).max(60).describe("Display name shown in the Saved dropdown (unique per user, max 60 chars)"),
42
42
  filters: filtersSchema,
43
43
  sortField: z.string().optional().describe("Sort field applied with the filter (default createdAt)"),
44
44
  sortOrder: z.enum(["asc", "desc"]).optional().describe("Sort direction (default desc)"),
45
45
  entityType: z.enum(["contact"]).optional().describe("Entity type (default 'contact')"),
46
46
  }, async (params) => {
47
- const result = await client.post("/api/v1/saved-filters", params);
47
+ // (Smart Views 09-24) saved filters are personal Smart Views now: the same body creates one (personal for a
48
+ // rep key; an admin key creates a SHARED view, admins have no private filters); POST /saved-filters is retired (410)
49
+ // personal: true keeps the saved-filter contract for EVERY key (an admin key creates a private view, never a shared one)
50
+ const result = await client.post("/api/v1/smart-views", { name: params.name, filters: params.filters, sortField: params.sortField, sortOrder: params.sortOrder, personal: true });
48
51
  return formatResult(result);
49
52
  });
50
- server.tool("conduyt_update_saved_filter", "Rename a saved filter or replace its filters/sort. Owner-only — you can only update your own saved filters. Use conduyt_list_saved_filters to find the id. Requires the 'saved-filters' scope.", {
53
+ server.tool("conduyt_update_saved_filter", "Rename a saved filter (a personal Smart View) or replace its filters/sort. Owner-only — a personal view is its owner's for every role. Use conduyt_list_saved_filters or conduyt_list_smart_views to find the id. Requires the 'saved-filters' scope.", {
51
54
  id: z.string().uuid().describe("Saved filter UUID (from conduyt_list_saved_filters)"),
52
55
  name: z.string().min(1).max(60).optional().describe("New display name (unique per user)"),
53
56
  filters: filtersSchema,
@@ -56,7 +59,7 @@ export function registerSavedFilterTools(server, client) {
56
59
  }, async ({ id, name, filters, sortField, sortOrder }) => {
57
60
  // Inline body (undefined fields drop out in JSON) — keeps the shape
58
61
  // readable by the contract verifier's static parser.
59
- const result = await client.patch(`/api/v1/saved-filters/${encodeURIComponent(id)}`, {
62
+ const result = await client.patch(`/api/v1/smart-views/${encodeURIComponent(id)}`, {
60
63
  name,
61
64
  filters,
62
65
  sortField,
@@ -64,12 +67,12 @@ export function registerSavedFilterTools(server, client) {
64
67
  });
65
68
  return formatResult(result);
66
69
  });
67
- server.tool("conduyt_delete_saved_filter", "Delete one of YOUR saved filters by id. Owner-only. Requires the 'saved-filters' scope.", {
70
+ server.tool("conduyt_delete_saved_filter", "Delete one of YOUR saved filters (a personal Smart View) by id. Owner-only. Requires the 'saved-filters' scope.", {
68
71
  // Strict UUID + encode — the id is interpolated into a DELETE path and
69
72
  // must never traverse out of the saved-filters endpoint.
70
73
  id: z.string().uuid().describe("Saved filter UUID (from conduyt_list_saved_filters)"),
71
74
  }, async ({ id }) => {
72
- const result = await client.del(`/api/v1/saved-filters/${encodeURIComponent(id)}`);
75
+ const result = await client.del(`/api/v1/smart-views/${encodeURIComponent(id)}`);
73
76
  return formatResult(result);
74
77
  });
75
78
  }
@@ -1,3 +1,17 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { ConduytClient } from "../client.js";
3
+ /**
4
+ * (Smart Views Codex r5) GET /smart-views is paged, 100 a page, and says so (meta.total, meta.has_more and an order
5
+ * stamp that moves on every reorder, edit, create and delete). EVERY page is walked under the first page's stamp,
6
+ * counts only on the first page (the expensive part); a page read after the order shifted restarts the walk, so an
7
+ * AI caller never silently loses a view past row 100 and never sees one twice.
8
+ */
9
+ export declare function listAllSmartViews(client: ConduytClient): Promise<{
10
+ data: Array<Record<string, unknown>>;
11
+ meta: {
12
+ total: number;
13
+ pages: number;
14
+ order_stamp: string;
15
+ };
16
+ }>;
3
17
  export declare function registerSmartViewTools(server: McpServer, client: ConduytClient): void;
@@ -14,11 +14,34 @@ const scheduleStepSchema = z.object({
14
14
  attemptNumber: z.number().int().min(1).max(20).describe("1 = the first call, 2 = the first retry, …"),
15
15
  delayMinutes: z.number().int().min(0).describe("Minutes to wait after the previous attempt before this one (0 = immediately)"),
16
16
  });
17
+ /**
18
+ * (Smart Views Codex r5) GET /smart-views is paged, 100 a page, and says so (meta.total, meta.has_more and an order
19
+ * stamp that moves on every reorder, edit, create and delete). EVERY page is walked under the first page's stamp,
20
+ * counts only on the first page (the expensive part); a page read after the order shifted restarts the walk, so an
21
+ * AI caller never silently loses a view past row 100 and never sees one twice.
22
+ */
23
+ export async function listAllSmartViews(client) {
24
+ for (let attempt = 0; attempt < 3; attempt += 1) {
25
+ const first = (await client.get("/api/v1/smart-views"));
26
+ const rows = [...first.data.data];
27
+ let meta = first.data.meta;
28
+ let shifted = false;
29
+ for (let page = 2; meta.has_more && page <= 50; page += 1) {
30
+ const next = (await client.get(`/api/v1/smart-views?page=${page}&counts=false`));
31
+ if (next.data.meta.order_stamp !== first.data.meta.order_stamp) {
32
+ shifted = true;
33
+ break;
34
+ }
35
+ rows.push(...next.data.data);
36
+ meta = next.data.meta;
37
+ }
38
+ if (!shifted)
39
+ return { data: rows, meta: { total: first.data.meta.total, pages: meta.page, order_stamp: first.data.meta.order_stamp } };
40
+ }
41
+ throw new Error("Smart Views kept changing while they were listed (a reorder or edit landed three times in a row); try again");
42
+ }
17
43
  export function registerSmartViewTools(server, client) {
18
- server.tool("conduyt_list_smart_views", "List the account's Smart Views in sidebar order with their lead counts and Smart Dialing settings (dialEnabled = it is a dialing priority; sortPosition = its rank; maxDialAttempts, maxDialAttemptsPerDay, dialCooldownMinutes, dialSchedule). Use it to answer \"what does the team dial first?\" or before changing a priority.", {}, async () => {
19
- const result = await client.get("/api/v1/smart-views");
20
- return formatResult(result);
21
- });
44
+ server.tool("conduyt_list_smart_views", "List the account's Smart Views in sidebar order with their lead counts, audience (groupId + group name, members, isPersonal) and Smart Dialing settings (dialEnabled = it is a dialing priority; sortPosition = its rank; maxDialAttempts, maxDialAttemptsPerDay, dialCooldownMinutes, dialSchedule). Use it to answer \"what does the team dial first?\" or before changing a priority.", {}, async () => formatResult(await listAllSmartViews(client)));
22
45
  server.tool("conduyt_update_smart_view_dialing", "Change one Smart View's Smart Dialing settings (admin key): make it a priority or not, the lifetime attempt cap, the per-day cap (null = no daily cap, counted in the account timezone), the cooldown between calls to the same lead, the shared pool and when a pool lead is assigned, and the retry schedule. Only the fields you pass change. Use conduyt_reorder_smart_views to change WHICH priority it is.", {
23
46
  id: z.string().uuid().describe("Smart View UUID (from conduyt_list_smart_views)"),
24
47
  dialEnabled: z.boolean().optional().describe("true = this view is a Smart Dialing priority; false = agents no longer dial it"),
@@ -70,10 +93,39 @@ export function registerSmartViewTools(server, client) {
70
93
  const result = await client.patch("/api/v1/smart-views/dial-order", { order });
71
94
  return formatResult(result);
72
95
  });
96
+ server.tool("conduyt_set_smart_view_audience", "Who a SHARED Smart View is for (admin key): everyone on the account, one Team group (its CURRENT members, resolved when the view is read, so a rep added to the group later sees it and dials from it), or picked reps. A group replaces picked reps and vice versa. Personal views (a rep's own) have no audience.", {
97
+ id: z.string().uuid().describe("Smart View UUID (from conduyt_list_smart_views)"),
98
+ audience: z.enum(["everyone", "group", "reps"]).describe("everyone | group (pass groupId) | reps (pass memberIds)"),
99
+ groupId: z.string().uuid().optional().describe("The Team group (conduyt_list_groups) when audience = group"),
100
+ memberIds: z.array(z.string().uuid()).min(1).max(200).optional().describe("The reps' user ids when audience = reps"),
101
+ }, async ({ id, audience, groupId, memberIds }) => {
102
+ if (audience === "group" && !groupId)
103
+ throw new Error("audience = group needs groupId");
104
+ if (audience === "reps" && (!memberIds || memberIds.length === 0))
105
+ throw new Error("audience = reps needs memberIds");
106
+ const result = await client.patch(`/api/v1/smart-views/${encodeURIComponent(id)}`, {
107
+ groupId: audience === "group" ? groupId : null,
108
+ members: audience === "reps" ? (memberIds ?? []).map((userId) => ({ userId })) : [],
109
+ });
110
+ return formatResult(result);
111
+ });
73
112
  server.tool("conduyt_reorder_smart_views", "Set the SIDEBAR order of Smart Views (admin key). Pass EVERY view id from conduyt_list_smart_views in the wanted order (max 200, no duplicates) — views you leave out keep a stale position, so always send the full list. For the dialing priority use conduyt_set_smart_dialing_order, which only needs the dial-enabled ids.", {
74
113
  order: z.array(z.string().uuid()).min(1).max(200).describe("Every Smart View UUID, first = highest priority"),
75
114
  }, async ({ order }) => {
76
115
  const result = await client.patch("/api/v1/smart-views/reorder", { order });
77
116
  return formatResult(result);
78
117
  });
118
+ server.tool("conduyt_move_smart_view", "Move ONE Smart View next to another in the sidebar order (admin key): pass the moved view's id and exactly one anchor, beforeId (it lands directly above that view) or afterId (directly below). Every other view keeps its relative position; no size limit, so use this on accounts with more than 200 views (conduyt_reorder_smart_views is capped at 200 ids). The order is the Smart Dialing priority for dial-enabled views.", {
119
+ id: z.string().uuid().describe("The Smart View to move"),
120
+ beforeId: z.string().uuid().optional().describe("Land directly ABOVE this view (pass this or afterId, not both)"),
121
+ afterId: z.string().uuid().optional().describe("Land directly BELOW this view (pass this or beforeId, not both)"),
122
+ }, async ({ id, beforeId, afterId }) => {
123
+ const anchors = [beforeId, afterId].filter(Boolean);
124
+ if (anchors.length !== 1)
125
+ throw new Error("Pass exactly one of beforeId or afterId.");
126
+ if (anchors[0] === id)
127
+ throw new Error("A view cannot be anchored to itself.");
128
+ const result = await client.patch("/api/v1/smart-views/reorder", { move: beforeId ? { id, beforeId } : { id, afterId } });
129
+ return formatResult(result);
130
+ });
79
131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.66.0",
3
+ "version": "4.67.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-24T04:33:02.436Z",
3
- "toolCount": 282,
4
- "totalEndpoints": 1000,
2
+ "generatedAt": "2026-09-25T04:02:01.551Z",
3
+ "toolCount": 284,
4
+ "totalEndpoints": 1003,
5
5
  "counts": {
6
- "covered": 262,
7
- "excluded": 293,
8
- "planned": 445,
6
+ "covered": 260,
7
+ "excluded": 295,
8
+ "planned": 448,
9
9
  "uncategorized": 0
10
10
  },
11
11
  "matrix": {
@@ -97,6 +97,14 @@
97
97
  "status": "excluded",
98
98
  "reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
99
99
  },
100
+ "POST /api/public/forms/:slug/upload-url": {
101
+ "status": "excluded",
102
+ "reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
103
+ },
104
+ "OPTIONS /api/public/forms/:slug/upload-url": {
105
+ "status": "excluded",
106
+ "reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
107
+ },
100
108
  "GET /api/v1/forms": {
101
109
  "status": "covered",
102
110
  "tools": [
@@ -145,6 +153,10 @@
145
153
  "status": "planned",
146
154
  "reason": "form management"
147
155
  },
156
+ "GET /api/v1/forms/:id/uploads": {
157
+ "status": "planned",
158
+ "reason": "form management"
159
+ },
148
160
  "POST /api/public/invoices/:id/payment-link": {
149
161
  "status": "excluded",
150
162
  "reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
@@ -3059,10 +3071,6 @@
3059
3071
  "status": "planned",
3060
3072
  "reason": "notification surfaces"
3061
3073
  },
3062
- "GET /api/v1/ops/capacity": {
3063
- "status": "planned",
3064
- "reason": "operator capacity dashboard (#51, shipped 2026-09) — read-only queue/park telemetry; a typed conduyt_capacity_snapshot tool is agent-relevant for throughput questions"
3065
- },
3066
3074
  "GET /api/v1/phone-numbers": {
3067
3075
  "status": "planned",
3068
3076
  "reason": "phone number inventory"
@@ -3578,22 +3586,16 @@
3578
3586
  "reason": "saved filter management"
3579
3587
  },
3580
3588
  "POST /api/v1/saved-filters": {
3581
- "status": "covered",
3582
- "tools": [
3583
- "conduyt_create_saved_filter"
3584
- ]
3589
+ "status": "planned",
3590
+ "reason": "saved filter management"
3585
3591
  },
3586
3592
  "PATCH /api/v1/saved-filters/:id": {
3587
- "status": "covered",
3588
- "tools": [
3589
- "conduyt_update_saved_filter"
3590
- ]
3593
+ "status": "planned",
3594
+ "reason": "saved filter management"
3591
3595
  },
3592
3596
  "DELETE /api/v1/saved-filters/:id": {
3593
- "status": "covered",
3594
- "tools": [
3595
- "conduyt_delete_saved_filter"
3596
- ]
3597
+ "status": "planned",
3598
+ "reason": "saved filter management"
3597
3599
  },
3598
3600
  "GET /api/v1/schema/api-catalog": {
3599
3601
  "status": "covered",
@@ -3985,15 +3987,15 @@
3985
3987
  "reason": "smart list management"
3986
3988
  },
3987
3989
  "GET /api/v1/smart-views": {
3990
+ "status": "planned",
3991
+ "reason": "smart view management beyond summaries"
3992
+ },
3993
+ "POST /api/v1/smart-views": {
3988
3994
  "status": "covered",
3989
3995
  "tools": [
3990
- "conduyt_list_smart_views"
3996
+ "conduyt_create_saved_filter"
3991
3997
  ]
3992
3998
  },
3993
- "POST /api/v1/smart-views": {
3994
- "status": "planned",
3995
- "reason": "smart view management beyond summaries"
3996
- },
3997
3999
  "GET /api/v1/smart-views/:id": {
3998
4000
  "status": "planned",
3999
4001
  "reason": "smart view management beyond summaries"
@@ -4001,12 +4003,16 @@
4001
4003
  "PATCH /api/v1/smart-views/:id": {
4002
4004
  "status": "covered",
4003
4005
  "tools": [
4004
- "conduyt_update_smart_view_dialing"
4006
+ "conduyt_update_saved_filter",
4007
+ "conduyt_update_smart_view_dialing",
4008
+ "conduyt_set_smart_view_audience"
4005
4009
  ]
4006
4010
  },
4007
4011
  "DELETE /api/v1/smart-views/:id": {
4008
- "status": "planned",
4009
- "reason": "smart view management beyond summaries"
4012
+ "status": "covered",
4013
+ "tools": [
4014
+ "conduyt_delete_saved_filter"
4015
+ ]
4010
4016
  },
4011
4017
  "GET /api/v1/smart-views/dial-order": {
4012
4018
  "status": "covered",
@@ -4023,7 +4029,8 @@
4023
4029
  "PATCH /api/v1/smart-views/reorder": {
4024
4030
  "status": "covered",
4025
4031
  "tools": [
4026
- "conduyt_reorder_smart_views"
4032
+ "conduyt_reorder_smart_views",
4033
+ "conduyt_move_smart_view"
4027
4034
  ]
4028
4035
  },
4029
4036
  "GET /api/v1/sms-providers": {
@@ -4339,6 +4346,10 @@
4339
4346
  "status": "planned",
4340
4347
  "reason": "team management subroutes beyond list/invite"
4341
4348
  },
4349
+ "POST /api/v1/users/me/welcomed": {
4350
+ "status": "planned",
4351
+ "reason": "team management subroutes beyond list/invite"
4352
+ },
4342
4353
  "GET /api/v1/warmy-engine/health": {
4343
4354
  "status": "planned",
4344
4355
  "reason": "email warm-up engine surfaces"