conduyt-mcp 4.9.0 → 4.11.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/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 500+ REST endpoints.
4
4
 
5
- ## 136 Tools
5
+ ## 161 Tools
6
6
 
7
7
  ### Discovery
8
8
  | Tool | Description |
@@ -252,6 +252,12 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
252
252
  | `conduyt_ai_task_segment_and_act` | AI task (preview only): segment by filter, then tag/assign/update — execution is human-session-only |
253
253
  | `conduyt_ai_task_pipeline_cleanup` | AI task (preview only): find stale deals and tag/move/notify/report — execution is human-session-only |
254
254
 
255
+ ### Privacy (GDPR)
256
+ | Tool | Description |
257
+ |------|-------------|
258
+ | `conduyt_export_contact_data` | GDPR data-portability export — full portable JSON of everything held about a contact (owner/admin) |
259
+ | `conduyt_forget_contact` | IRREVERSIBLE GDPR right-to-be-forgotten erasure — requires `confirm='FORGET'` (owner only) |
260
+
255
261
  ## Setup
256
262
 
257
263
  ```bash
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ import { registerTemplateTools } from "./tools/templates.js";
34
34
  import { registerDashboardTools } from "./tools/dashboard.js";
35
35
  import { registerAiExtendedTools } from "./tools/ai-extended.js";
36
36
  import { registerWorkflowSandboxTools } from "./tools/workflow-sandbox.js";
37
+ import { registerPrivacyTools } from "./tools/privacy.js";
37
38
  const apiUrl = process.env.CONDUYT_API_URL;
38
39
  const apiKey = process.env.CONDUYT_API_KEY;
39
40
  if (!apiUrl || !apiKey) {
@@ -85,5 +86,6 @@ registerTemplateTools(server, client);
85
86
  registerDashboardTools(server, client);
86
87
  registerAiExtendedTools(server, client);
87
88
  registerWorkflowSandboxTools(server, client);
89
+ registerPrivacyTools(server, client);
88
90
  const transport = new StdioServerTransport();
89
91
  await server.connect(transport);
@@ -127,4 +127,24 @@ export function registerBulkTools(server, client) {
127
127
  const result = await client.post("/api/v1/bulk/deals/update", params);
128
128
  return formatResult(result);
129
129
  });
130
+ server.tool("conduyt_import_preflight", "Pre-check a contact import BEFORE running it — returns criticalWarnings (phone-less list bound for SMS, heavy create-only duplicate skips, double-mapped columns), duplicate/existing-match estimates, invalid emails, DNC/litigator hits, SMS-provider readiness, and plan-limit impact. Same engine the import wizard uses; call this before conduyt_ai_import_contacts on any sizeable list.", {
131
+ objectType: z.enum(["contacts", "opportunities", "companies"]).default("contacts").describe("What the import creates"),
132
+ importMode: z.enum(["create_and_update", "create_only", "update_only"]).optional().describe("Duplicate handling mode the import will use"),
133
+ totalRows: z.number().describe("Total rows in the file/payload"),
134
+ emails: z.array(z.string()).optional().describe("All email values from the file (order-free)"),
135
+ phones: z.array(z.string()).optional().describe("All phone values from the file (order-free)"),
136
+ mappedFields: z.array(z.string()).optional().describe("The Conduyt field names your columns map to, e.g. ['firstName','phone']"),
137
+ removeDuplicates: z.boolean().optional().describe("Whether explicit dedupe is on"),
138
+ duplicateCheckFields: z.array(z.enum(["email", "phone"])).optional().describe("Identifiers explicit dedupe checks"),
139
+ rowIdentifiers: z.array(z.object({
140
+ email: z.string().nullable().optional(),
141
+ phone: z.string().nullable().optional(),
142
+ country: z.string().nullable().optional(),
143
+ firstName: z.string().nullable().optional(),
144
+ lastName: z.string().nullable().optional(),
145
+ })).optional().describe("Per-row {email, phone, country, firstName, lastName} for the exact name-aware skip estimate (recommended; falls back to a conservative estimate without it)"),
146
+ }, async (params) => {
147
+ const result = await client.post("/api/v1/imports/preflight", params);
148
+ return formatResult(result);
149
+ });
130
150
  }
@@ -100,4 +100,11 @@ export function registerContactTools(server, client) {
100
100
  const result = await client.post(`/api/v1/deals/${dealId}/contacts`, { contactId, role });
101
101
  return formatResult(result);
102
102
  });
103
+ server.tool("conduyt_verify_line_type", "Verify contacts' phone LINE TYPE (mobile/landline/VoIP) + carrier + validity via the tenant's own Twilio Lookup, stamping the results on each contact (Phone Verification custom fields). OPT-IN + TENANT-PAID (~$0.008/number on their Twilio): requires the account's lineTypeVerificationEnabled Twilio setting, configured Twilio credentials, and org-wide contact access (owner/admin/view_all). BATCHED + idempotent: each call verifies up to 50 not-yet-stamped contacts and returns { verified, remaining, done, byLineType } — CALL REPEATEDLY until done=true. Provide exactly ONE scope: smartListId or contactIds.", {
104
+ smartListId: z.string().uuid().optional().describe("Verify the unstamped members of this smart list"),
105
+ contactIds: z.array(z.string().uuid()).max(50000).optional().describe("Explicit contact UUIDs to verify (max 50,000)"),
106
+ }, async (params) => {
107
+ const result = await client.post("/api/v1/contacts/verify-line-type", params);
108
+ return formatResult(result);
109
+ });
103
110
  }
@@ -66,4 +66,22 @@ export function registerMessagingTools(server, client) {
66
66
  const result = await client.get(`/api/v1/messages?${params}`);
67
67
  return formatResult(result);
68
68
  });
69
+ server.tool("conduyt_sms_delivery_report", "SMS provider delivery-outcome report for external-workflow SMS drips: per-automation entered vs delivered / rejected (invalid, litigator, DNC) / provider-error / workflow dead-end, plus a send-attempt 'why rejected' reason breakdown (line_type = 'not mobile' often WRONGLY rejected; landline_skipped / no_phone_skipped = Conduyt's own recorded skips). Use to answer 'what did the SMS provider do with our drips?'", {
70
+ from: z.string().optional().describe("ISO start (omit both for the last 180 days; spans clamp to 180d)"),
71
+ to: z.string().optional().describe("ISO end"),
72
+ automationId: z.string().uuid().optional().describe("Scope to one automation"),
73
+ smartListId: z.string().uuid().optional().describe("Scope to contacts in this smart list"),
74
+ }, async ({ from, to, automationId, smartListId }) => {
75
+ const params = new URLSearchParams();
76
+ if (from)
77
+ params.set("from", from);
78
+ if (to)
79
+ params.set("to", to);
80
+ if (automationId)
81
+ params.set("automationId", automationId);
82
+ if (smartListId)
83
+ params.set("smartListId", smartListId);
84
+ const result = await client.get(`/api/v1/reports/sms-delivery?${params}`);
85
+ return formatResult(result);
86
+ });
69
87
  }
@@ -0,0 +1,11 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ /**
4
+ * GDPR privacy tools — tri-surface parity for the /gdpr-export and /gdpr-forget
5
+ * endpoints (audit F7). The API enforces role independently (export =
6
+ * owner/admin, forget = owner-only); these tools work with whatever role the
7
+ * API key carries. The forget is IRREVERSIBLE, so the tool requires an explicit
8
+ * confirm="FORGET" literal (mirroring the API contract) — a model cannot erase a
9
+ * contact without deliberately supplying it.
10
+ */
11
+ export declare function registerPrivacyTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ /**
4
+ * GDPR privacy tools — tri-surface parity for the /gdpr-export and /gdpr-forget
5
+ * endpoints (audit F7). The API enforces role independently (export =
6
+ * owner/admin, forget = owner-only); these tools work with whatever role the
7
+ * API key carries. The forget is IRREVERSIBLE, so the tool requires an explicit
8
+ * confirm="FORGET" literal (mirroring the API contract) — a model cannot erase a
9
+ * contact without deliberately supplying it.
10
+ */
11
+ export function registerPrivacyTools(server, client) {
12
+ server.tool("conduyt_export_contact_data", "GDPR data-portability export: returns the full portable JSON of everything held about a contact — profile, custom fields, tags, messages, notes, deals, activities, calls, appointments, files, and consent/suppression records. Read-only. Requires an owner or admin API key (the API enforces this).", {
13
+ // Strict UUID + encode: a raw id with slashes could otherwise traverse
14
+ // into a different subroute. Keep this export-only regardless of key role.
15
+ id: z.string().uuid().describe("Contact UUID to export"),
16
+ }, async ({ id }) => {
17
+ const result = await client.get(`/api/v1/contacts/${encodeURIComponent(id)}/gdpr-export`);
18
+ return formatResult(result);
19
+ });
20
+ server.tool("conduyt_forget_contact", "IRREVERSIBLE GDPR right-to-be-forgotten erasure: permanently anonymizes the contact and erases their personal data EVERYWHERE it appears — messages, notes, deals, files, automations, and exports — across the whole account. Suppression/consent tombstones are kept so they are not re-contacted. THERE IS NO UNDO. Requires an owner API key (the API enforces this) and an explicit confirm='FORGET'. On a large tenant this can legitimately take tens of seconds. A 409 is retryable (a concurrent merge or in-flight automation) — retry once.", {
21
+ // Strict UUID + encode — this must stay a forget of exactly this contact,
22
+ // never a traversal into another destructive endpoint.
23
+ id: z.string().uuid().describe("Contact UUID to erase"),
24
+ confirm: z
25
+ .literal("FORGET")
26
+ .describe("Must be exactly the string 'FORGET'. This is a hard safety gate for an irreversible erasure — do not supply it unless the erasure is intended."),
27
+ }, async ({ id, confirm }) => {
28
+ const result = await client.post(`/api/v1/contacts/${encodeURIComponent(id)}/gdpr-forget`, { confirm });
29
+ return formatResult(result);
30
+ });
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.9.0",
3
+ "version": "4.11.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",