conduyt-mcp 4.105.1 → 4.105.2

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-accessible tools. Covers the core CRM surface (contacts, deals, pipelines, custom fields, automations, messaging, and more); use `conduyt_api_catalog` to discover the full set of 600+ REST endpoints.
4
4
 
5
- ## 378 Tools
5
+ ## 383 Tools
6
6
 
7
7
  ### Discovery
8
8
  | Tool | Description |
@@ -91,6 +91,13 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
91
91
  | `conduyt_publish_automation` | Push draft actions to live |
92
92
  | `conduyt_resolve_names` | Convert tag/pipeline/user names to UUIDs |
93
93
 
94
+ ### Lead distribution (read-only)
95
+ | Tool | Description |
96
+ |------|-------------|
97
+ | `conduyt_get_distribution_step` | One Distribution step's live table: team, Receiving, tiers, caps, this step's counts, the queue and the next lead (or preview an unsaved table) |
98
+ | `conduyt_list_distribution_steps` | Every workflow step in Distribution mode (Settings > Lead Routing) |
99
+ | `conduyt_list_assignment_reasons` | The assignment reasons, resolved: by deal, contact, step, person, outcome, days |
100
+
94
101
  ### Workflow Sandbox
95
102
  | Tool | Description |
96
103
  |------|-------------|
@@ -49,6 +49,7 @@ import { registerSmartViewTools } from "./tools/smart-views.js";
49
49
  import { registerAccountExportTools } from "./tools/account-exports.js";
50
50
  import { registerLifecycleTools } from "./tools/lifecycle.js";
51
51
  import { registerLeadPoolTools } from "./tools/lead-pool.js";
52
+ import { registerLeadDistributionTools } from "./tools/lead-distribution.js";
52
53
  import { registerReplyCaptureTools } from "./tools/reply-capture.js";
53
54
  import { registerCallFlowTools } from "./tools/call-flows.js";
54
55
  import { registerProjectBlueTools } from "./tools/project-blue.js";
@@ -112,6 +113,7 @@ export function registerAllTools(server, client) {
112
113
  registerAccountExportTools(server, client);
113
114
  registerLifecycleTools(server, client);
114
115
  registerLeadPoolTools(server, client);
116
+ registerLeadDistributionTools(server, client);
115
117
  registerReplyCaptureTools(server, client);
116
118
  registerCallFlowTools(server, client);
117
119
  registerProjectBlueTools(server, client);
@@ -9,6 +9,10 @@ export const TOOL_ANNOTATIONS = {
9
9
  conduyt_api_catalog: ro("Get API Catalog"),
10
10
  conduyt_check_permissions: ro("Check Permissions"),
11
11
  conduyt_search: ro("Search CRM Records"),
12
+ // (#996 UI) lead distribution reads (the step table, the Settings overview, the reasons); the draft preview POST writes nothing
13
+ conduyt_get_distribution_step: ro("Get Distribution Step"),
14
+ conduyt_list_distribution_steps: ro("List Distribution Steps"),
15
+ conduyt_list_assignment_reasons: ro("List Assignment Reasons"),
12
16
  conduyt_mcp_coverage: ro("Get MCP Coverage Matrix"),
13
17
  conduyt_list_contacts: ro("List Contacts"),
14
18
  conduyt_count_contacts: ro("Count Contacts"),
@@ -0,0 +1,28 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ /**
4
+ * (#996 UI, tri-surface parity) the READ side of lead distribution (the assign_to_user step's "distribution" mode: tiers, caps
5
+ * per hour / day / week / month, an overflow), the same reads the step editor, Settings > Lead Routing and the assignment
6
+ * history use. All three write nothing; the table itself is edited through the workflow (conduyt_update_automation, the
7
+ * step's config) exactly as before.
8
+ * conduyt_get_distribution_step GET /automations/{id}/distribution?node_id= (the step as the runner executes it), or with
9
+ * `config` POST /automations/{id}/distribution (an unsaved draft table, still read-only)
10
+ * conduyt_list_distribution_steps GET /automations/distribution-steps
11
+ * conduyt_list_assignment_reasons GET /activities/assignment-reasons
12
+ */
13
+ export declare const ASSIGNMENT_OUTCOMES: readonly ["assigned", "catch_all", "contact_owner", "kept", "pool", "nobody"];
14
+ /** the query string the reasons route reads (repeated user_id / outcome, the rest once) */
15
+ export declare function assignmentReasonsQuery(p: {
16
+ deal_id?: string;
17
+ contact_id?: string;
18
+ automation_id?: string;
19
+ node_id?: string;
20
+ user_ids?: string[];
21
+ outcomes?: string[];
22
+ activity_ids?: string[];
23
+ from?: string;
24
+ to?: string;
25
+ per_page?: number;
26
+ cursor?: string;
27
+ }): string;
28
+ export declare function registerLeadDistributionTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ /**
4
+ * (#996 UI, tri-surface parity) the READ side of lead distribution (the assign_to_user step's "distribution" mode: tiers, caps
5
+ * per hour / day / week / month, an overflow), the same reads the step editor, Settings > Lead Routing and the assignment
6
+ * history use. All three write nothing; the table itself is edited through the workflow (conduyt_update_automation, the
7
+ * step's config) exactly as before.
8
+ * conduyt_get_distribution_step GET /automations/{id}/distribution?node_id= (the step as the runner executes it), or with
9
+ * `config` POST /automations/{id}/distribution (an unsaved draft table, still read-only)
10
+ * conduyt_list_distribution_steps GET /automations/distribution-steps
11
+ * conduyt_list_assignment_reasons GET /activities/assignment-reasons
12
+ */
13
+ export const ASSIGNMENT_OUTCOMES = ["assigned", "catch_all", "contact_owner", "kept", "pool", "nobody"];
14
+ const day = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "a calendar day, YYYY-MM-DD");
15
+ /** the query string the reasons route reads (repeated user_id / outcome, the rest once) */
16
+ export function assignmentReasonsQuery(p) {
17
+ const q = new URLSearchParams();
18
+ for (const k of ["deal_id", "contact_id", "automation_id", "node_id", "from", "to", "cursor"])
19
+ if (p[k])
20
+ q.set(k, p[k]);
21
+ for (const a of [...new Set(p.activity_ids ?? [])])
22
+ q.append("activity_id", a);
23
+ for (const u of [...new Set(p.user_ids ?? [])])
24
+ q.append("user_id", u);
25
+ for (const o of [...new Set(p.outcomes ?? [])])
26
+ q.append("outcome", o);
27
+ if (p.per_page !== undefined)
28
+ q.set("per_page", String(p.per_page));
29
+ return q.toString();
30
+ }
31
+ export function registerLeadDistributionTools(server, client) {
32
+ server.tool("conduyt_get_distribution_step", "Read ONE Distribution step's live table (assign_to_user with mode \"distribution\"), read-only: the team (a member group's active members, or the listed people, flagged when someone left the account), each person's Receiving status, tier (highest / high / low / lowest, absent = high), own caps (a period present overrides the team default; null = own unlimited; absent = inherits) and effective caps per hour / day / week / month, THIS step's assignment counts for the current periods (the account timezone, the week from Monday), the ordered queue (`order` 1 = next; `skipped` = away, not_on_account or the period whose cap is reached; `zeroCap` = a cap of 0, never gets leads), the next lead from the workflow dry run's own plan (`next.reason` is the exact reason line the step would write; `next.dryRun` the dry run's line), `problems` in plain English (a person who left, the catch-all removed). Without `config` it reads the step as the runner executes it (the published graph, else the draft; 404 when the workflow has no Distribution step with that node_id; 422 when the stored table does not read). With `config` (the step's whole assign_to_user config, mode \"distribution\") it previews that unsaved table instead and writes nothing (400 when it does not read). Permission automations.view; a deal-less API key gets 404 (every Distribution workflow assigns deals).", {
33
+ automation_id: z.string().uuid().describe("The workflow (automation) id"),
34
+ node_id: z.string().min(1).max(200).describe("The step's node id in the graph (a linear workflow's action id, else #<index>)"),
35
+ config: z.record(z.unknown()).optional().describe("Optional: an unsaved assign_to_user config to preview (mode \"distribution\", groupId OR userIds, tiers, caps, overflow, applyTo deal | both)"),
36
+ }, async ({ automation_id, node_id, config }) => formatResult(config
37
+ ? await client.post(`/api/v1/automations/${automation_id}/distribution`, { nodeId: node_id, config })
38
+ : await client.get(`/api/v1/automations/${automation_id}/distribution?node_id=${encodeURIComponent(node_id)}`)));
39
+ server.tool("conduyt_list_distribution_steps", "Settings > Lead Routing, \"Distribution in workflows\" (read-only): every workflow step running the assign_to_user Distribution mode, from the graph the runner executes, with its team (group name, or the count of picked people), receivingNow / total, who gets the next lead (`next`, the dry run's plan), the overflow (pool, none, or the catch-all person), assignedToday (this step's assignments since the account's midnight) and `problem` when the stored table would fail at run time. An aggregate: at most 200 steps (meta.max_steps) from at most 200 workflows (meta.max_workflows), newest changed first; meta.truncated says more exist (either cap). This is NOT the older account-wide Lead Routing Rules router (/api/v1/lead-routing). Permission automations.view; a deal-less API key is served an empty list.", {}, async () => formatResult(await client.get("/api/v1/automations/distribution-steps")));
40
+ server.tool("conduyt_list_assignment_reasons", "The lead distribution reasons (activities of type assignment_reason), newest first, read-only, each resolved: `reason` (the exact line, e.g. \"Assigned to Heather Henson: tier highest, 3 of 10 today\" or \"Sent to the pool: everyone was off or at cap\"), `outcome` (assigned, catch_all, contact_owner, kept, pool, nobody), the person, the deal and contact, the workflow and step (`automation`, `nodeId`, `stepLabel`), tier and today's count, `skipped` (who was passed over: a cap period, or away = not receiving leads) and `ledgerId` (the assignment_ledger row the assignment counted toward the caps; null for the overflow outcomes that assign nobody). Every reason sits on BOTH the deal and its contact, so contact_id gives the contact's whole routing history across its deals. Filters: deal_id, contact_id, automation_id + node_id (one step's history; node_id needs automation_id), user_ids (up to 50 distinct), outcomes, activity_ids (up to 100 distinct: the reasons of activities you already hold), from / to (days in the account timezone). A kept outcome's person is the owner the card stayed with. per_page 1-100 (default 25); pass meta.nextCursor as cursor for the next page. Permission activities.view, the caller's visible contacts and deals only.", {
41
+ deal_id: z.string().uuid().optional(),
42
+ contact_id: z.string().uuid().optional(),
43
+ automation_id: z.string().uuid().optional().describe("The workflow; with node_id, one step's history"),
44
+ node_id: z.string().min(1).max(200).optional().describe("The step's node id (needs automation_id)"),
45
+ // (r2, Codex r1 P3) repeats are removed BEFORE the caps, as the route does (50 people, 100 activities)
46
+ user_ids: z.array(z.string().uuid()).optional().describe("Only reasons that assigned (or kept) one of these people; up to 50 distinct"),
47
+ outcomes: z.array(z.enum(ASSIGNMENT_OUTCOMES)).optional(),
48
+ activity_ids: z.array(z.string().uuid()).optional().describe("The reasons of these assignment_reason activities (a feed resolving the rows it loaded); up to 100 distinct"),
49
+ from: day.optional().describe("First day, YYYY-MM-DD, in the account timezone"),
50
+ to: day.optional().describe("Last day, YYYY-MM-DD, in the account timezone (inclusive)"),
51
+ per_page: z.number().int().min(1).max(100).optional(),
52
+ cursor: z.string().min(1).optional().describe("meta.nextCursor of the previous page"),
53
+ }, async (p) => {
54
+ if (p.node_id && !p.automation_id)
55
+ throw new Error("node_id needs automation_id (a step belongs to one workflow)");
56
+ if (new Set(p.user_ids ?? []).size > 50)
57
+ throw new Error("user_ids takes at most 50 distinct people");
58
+ if (new Set(p.activity_ids ?? []).size > 100)
59
+ throw new Error("activity_ids takes at most 100 distinct activities");
60
+ return formatResult(await client.get(`/api/v1/activities/assignment-reasons?${assignmentReasonsQuery(p)}`));
61
+ });
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.105.1",
3
+ "version": "4.105.2",
4
4
  "mcpName": "com.conduyt/mcp",
5
5
  "description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
6
6
  "type": "module",
@@ -1,10 +1,10 @@
1
1
  {
2
- "generatedAt": "2026-11-05T07:30:55.402Z",
3
- "toolCount": 380,
4
- "totalEndpoints": 1143,
2
+ "generatedAt": "2026-11-05T14:27:30.253Z",
3
+ "toolCount": 383,
4
+ "totalEndpoints": 1150,
5
5
  "counts": {
6
- "covered": 333,
7
- "excluded": 351,
6
+ "covered": 337,
7
+ "excluded": 354,
8
8
  "planned": 459,
9
9
  "uncategorized": 0
10
10
  },
@@ -305,6 +305,12 @@
305
305
  "status": "planned",
306
306
  "reason": "activity detail reads beyond the log tools"
307
307
  },
308
+ "GET /api/v1/activities/assignment-reasons": {
309
+ "status": "covered",
310
+ "tools": [
311
+ "conduyt_list_assignment_reasons"
312
+ ]
313
+ },
308
314
  "GET /api/v1/activity/export.csv": {
309
315
  "status": "covered",
310
316
  "tools": [
@@ -355,6 +361,14 @@
355
361
  "status": "excluded",
356
362
  "reason": "super-admin platform operations (Conduyt staff only)"
357
363
  },
364
+ "GET /api/v1/admin/external-deletions": {
365
+ "status": "excluded",
366
+ "reason": "super-admin platform operations (Conduyt staff only)"
367
+ },
368
+ "POST /api/v1/admin/external-deletions/:id/replay": {
369
+ "status": "excluded",
370
+ "reason": "super-admin platform operations (Conduyt staff only)"
371
+ },
358
372
  "POST /api/v1/admin/impersonate": {
359
373
  "status": "excluded",
360
374
  "reason": "super-admin platform operations (Conduyt staff only)"
@@ -701,6 +715,18 @@
701
715
  "status": "planned",
702
716
  "reason": "automation subroutes beyond the workflow-builder tools (step-logs detail, node ops)"
703
717
  },
718
+ "GET /api/v1/automations/:id/distribution": {
719
+ "status": "covered",
720
+ "tools": [
721
+ "conduyt_get_distribution_step"
722
+ ]
723
+ },
724
+ "POST /api/v1/automations/:id/distribution": {
725
+ "status": "covered",
726
+ "tools": [
727
+ "conduyt_get_distribution_step"
728
+ ]
729
+ },
704
730
  "POST /api/v1/automations/:id/dry-run": {
705
731
  "status": "covered",
706
732
  "tools": [
@@ -750,6 +776,12 @@
750
776
  "status": "planned",
751
777
  "reason": "automation subroutes beyond the workflow-builder tools (step-logs detail, node ops)"
752
778
  },
779
+ "GET /api/v1/automations/distribution-steps": {
780
+ "status": "covered",
781
+ "tools": [
782
+ "conduyt_list_distribution_steps"
783
+ ]
784
+ },
753
785
  "GET /api/v1/automations/events": {
754
786
  "status": "planned",
755
787
  "reason": "automation subroutes beyond the workflow-builder tools (step-logs detail, node ops)"
@@ -2068,6 +2100,10 @@
2068
2100
  "status": "excluded",
2069
2101
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
2070
2102
  },
2103
+ "GET /api/v1/cron/reap-expired-sessions": {
2104
+ "status": "excluded",
2105
+ "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
2106
+ },
2071
2107
  "GET /api/v1/cron/reap-orphaned-executions": {
2072
2108
  "status": "excluded",
2073
2109
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"