conduyt-mcp 4.72.0 → 4.73.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 +3 -3
- package/dist/tools/deals.js +2 -1
- package/dist/tools/sequences.js +12 -5
- package/package.json +1 -1
- package/scripts/coverage-matrix.json +32 -4
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Conduyt MCP Server
|
|
2
2
|
|
|
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
|
|
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
5
|
## 299 Tools
|
|
6
6
|
|
|
7
7
|
### Discovery
|
|
8
8
|
| Tool | Description |
|
|
9
9
|
|------|-------------|
|
|
10
|
-
| `conduyt_api_catalog` | Discover all
|
|
10
|
+
| `conduyt_api_catalog` | Discover all 600+ API endpoints |
|
|
11
11
|
| `conduyt_check_permissions` | Introspect current API key permissions |
|
|
12
12
|
| `conduyt_search` | Global search across contacts, deals, companies, tasks |
|
|
13
13
|
|
|
@@ -26,7 +26,7 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
26
26
|
| `conduyt_list_deals` | List deals by pipeline, stage, or assignee |
|
|
27
27
|
| `conduyt_get_deal` | Get deal details with history, including custom-field values |
|
|
28
28
|
| `conduyt_create_deal` | Create a deal with full attributes — currency (e.g. GBP), probability, priority, expected close date, contact/company, and structured custom-field values |
|
|
29
|
-
| `conduyt_update_deal` | Move stage, change value/currency, set probability or close date, reassign, mark won/lost, or write custom-field values |
|
|
29
|
+
| `conduyt_update_deal` | Move stage, change value/currency, set probability or close date, reassign, mark won/lost, or write custom-field values; optional `expectedUpdatedAt` pins the `updatedAt` you read, and a stale pin returns 409 `deal_changed` with the current `updatedAt` and writes nothing |
|
|
30
30
|
| `conduyt_import_deals` | Bulk-import deals (pipeline/stage by name, contact by email) via the canonical import engine — dedupes, side-effect-free, owner/admin only |
|
|
31
31
|
|
|
32
32
|
### Tasks
|
package/dist/tools/deals.js
CHANGED
|
@@ -105,7 +105,8 @@ export function registerDealTools(server, client) {
|
|
|
105
105
|
assignedTo: z.string().optional().describe("Reassign to user (owner) UUID"),
|
|
106
106
|
source: z.string().trim().min(1).max(500).optional().describe("Attribution source for THIS deal (the trigger that produced it) — distinct from the contact's first-touch source. Every deal keeps a source (#129): it can be changed, never cleared (null/blank are rejected). 1-500 chars."),
|
|
107
107
|
lostReason: z.string().optional().describe("Reason for a lost/disqualified deal. Some stages REQUIRE this on entry (the move fails with code STAGE_REQUIREMENTS listing lostReasonOptions when the stage has a configured dropdown — resend with one of those exact values)."),
|
|
108
|
-
customFields: z.record(z.any()).optional().describe("Structured custom-field values to merge, keyed by field key"),
|
|
108
|
+
customFields: z.record(z.any()).optional().describe("Structured custom-field values to merge, keyed by field key (send null for a key to clear it; keys you omit keep their current values)"),
|
|
109
|
+
expectedUpdatedAt: z.string().datetime({ message: "expectedUpdatedAt must be the deal's updatedAt exactly as the API returned it (UTC, trailing Z, e.g. 2026-10-06T23:19:00.000Z)" }).optional().describe("Optional: the deal's updatedAt from your last read, ISO 8601 exactly as returned (e.g. 2026-10-06T23:19:00.000Z). If the deal changed since, the update is refused (409, code deal_changed, with the current updatedAt) and nothing is written — read the deal again and retry. Omit for last-write-wins per field."),
|
|
109
110
|
}, async ({ id, ...updates }) => {
|
|
110
111
|
const result = await client.patch(`/api/v1/deals/${id}`, updates);
|
|
111
112
|
return formatResult(result);
|
package/dist/tools/sequences.js
CHANGED
|
@@ -20,7 +20,7 @@ const settingsFields = {
|
|
|
20
20
|
};
|
|
21
21
|
const uuid = (what) => z.string().uuid().describe(what);
|
|
22
22
|
/** how a run ended (enrollment endedReason), and the run statuses, as GET /emails/sequences/{id}/enrollments answers them */
|
|
23
|
-
const RUN_READING = "Each run carries status (active | paused | completed | failed | cancelled | unsubscribed) and endedReason (completed | replied | meeting | opted_out | bounced | deal_closed | removed; null while the run is live). A paused run with no endedReason is LIVE (the sequence is Off or archived; it resumes when turned On); a paused or cancelled run with an endedReason has ended.";
|
|
23
|
+
const RUN_READING = "Each run carries status (active | paused | completed | failed | cancelled | unsubscribed) and endedReason (completed | replied | meeting | opted_out | bounced | deal_closed | removed | failed | no_phone; null while the run is live). A paused run with no endedReason is LIVE (the sequence is Off or archived; it resumes when turned On); a failed run with no endedReason is ALSO LIVE (an ambiguous send outcome — a timeout, an unknown provider answer — that a late provider acceptance can still revive in place), while failed WITH endedReason \"failed\" is TERMINAL (a definitive worker or repair failure, no revival). no_phone is SMS-only: the run ended because the contact had no usable phone. A paused, failed or cancelled run with an endedReason has ended.";
|
|
24
24
|
const stepSchema = z.object({
|
|
25
25
|
templateId: z.string().uuid().describe("An active template of the sequence's channel (Email Templates or SMS Templates)"),
|
|
26
26
|
waitDays: z.number().int().min(0).optional().describe("Days after the previous step (the first step sends at enrollment)"),
|
|
@@ -118,11 +118,18 @@ export function registerSequenceTools(server, client) {
|
|
|
118
118
|
});
|
|
119
119
|
server.tool("conduyt_list_sequence_enrollments", `List the people in a sequence, newest run first: each run's contact, currentStep, nextSendAt, enrolledAt, completedAt, lastError and engagement (sent, opened, clicked). ${RUN_READING}`, {
|
|
120
120
|
sequenceId: uuid("Sequence UUID"),
|
|
121
|
-
status: z.enum(["active", "paused", "completed", "failed", "cancelled", "unsubscribed"]).optional().describe("Only runs with this status"),
|
|
122
|
-
all_active: z.boolean().optional().describe("true = EVERY live run (active, paused with no endedReason, failed with no endedReason) in one answer, no paging and no engagement; refused (422 too_many_live_runs) past 5,000"),
|
|
123
|
-
page: z.number().optional().describe("Page number (default 1)"),
|
|
124
|
-
per_page: z.number().optional().describe("Results per page (default 50, max 200)"),
|
|
121
|
+
status: z.enum(["active", "paused", "completed", "failed", "cancelled", "unsubscribed"]).optional().describe("Only runs with this status. Refused together with all_active=true — see all_active"),
|
|
122
|
+
all_active: z.boolean().optional().describe("true = EVERY live run (active, paused with no endedReason, failed with no endedReason) in one answer, no paging and no engagement; refused (422 too_many_live_runs) past 5,000. Pass it ALONE: status, page and per_page are refused together with all_active=true, because a status filter or pagination would silently cut the live set a rollback page relies on being complete"),
|
|
123
|
+
page: z.number().optional().describe("Page number (default 1). Refused together with all_active=true — see all_active"),
|
|
124
|
+
per_page: z.number().optional().describe("Results per page (default 50, max 200). Refused together with all_active=true — see all_active"),
|
|
125
125
|
}, async ({ sequenceId, status, all_active, page, per_page }) => {
|
|
126
|
+
// (round 2 Codex finding, high) all_active=true promises the COMPLETE live set. Letting status or pagination
|
|
127
|
+
// ride along would let the live route intersect them and silently return a subset while the caller believes
|
|
128
|
+
// it holds every live run (the risk: a rollback/bulk-unenroll agent leaves contacts mid-sequence). Refuse
|
|
129
|
+
// locally, before any request, rather than letting the subset through.
|
|
130
|
+
if (all_active && (status !== undefined || page !== undefined || per_page !== undefined)) {
|
|
131
|
+
throw new Error("all_active=true already returns every live run in one answer — pass it alone. status, page and per_page are refused together with all_active=true because they would cut the set instead of returning the complete one.");
|
|
132
|
+
}
|
|
126
133
|
const params = new URLSearchParams();
|
|
127
134
|
if (status)
|
|
128
135
|
params.set("status", status);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-10-
|
|
2
|
+
"generatedAt": "2026-10-04T13:54:51.239Z",
|
|
3
3
|
"toolCount": 299,
|
|
4
|
-
"totalEndpoints":
|
|
4
|
+
"totalEndpoints": 1043,
|
|
5
5
|
"counts": {
|
|
6
6
|
"covered": 273,
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
7
|
+
"excluded": 317,
|
|
8
|
+
"planned": 453,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -189,6 +189,14 @@
|
|
|
189
189
|
"status": "planned",
|
|
190
190
|
"reason": "invoicing"
|
|
191
191
|
},
|
|
192
|
+
"POST /api/v1/invoices/:id/overpayments/:overpaymentId/manual-refund": {
|
|
193
|
+
"status": "planned",
|
|
194
|
+
"reason": "invoicing"
|
|
195
|
+
},
|
|
196
|
+
"POST /api/v1/invoices/:id/overpayments/:overpaymentId/refund": {
|
|
197
|
+
"status": "planned",
|
|
198
|
+
"reason": "invoicing"
|
|
199
|
+
},
|
|
192
200
|
"POST /api/v1/invoices/:id/payment-link": {
|
|
193
201
|
"status": "planned",
|
|
194
202
|
"reason": "invoicing"
|
|
@@ -235,6 +243,14 @@
|
|
|
235
243
|
"status": "excluded",
|
|
236
244
|
"reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
|
|
237
245
|
},
|
|
246
|
+
"GET /api/public/unsubscribe/confirm/:token": {
|
|
247
|
+
"status": "excluded",
|
|
248
|
+
"reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
|
|
249
|
+
},
|
|
250
|
+
"POST /api/public/unsubscribe/confirm/:token": {
|
|
251
|
+
"status": "excluded",
|
|
252
|
+
"reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
|
|
253
|
+
},
|
|
238
254
|
"GET /api/track/click/:messageId": {
|
|
239
255
|
"status": "excluded",
|
|
240
256
|
"reason": "email open/click tracking pixels and redirects — recipient-facing"
|
|
@@ -1865,6 +1881,14 @@
|
|
|
1865
1881
|
"status": "excluded",
|
|
1866
1882
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1867
1883
|
},
|
|
1884
|
+
"GET /api/v1/cron/reconcile-overpayment-refunds": {
|
|
1885
|
+
"status": "excluded",
|
|
1886
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1887
|
+
},
|
|
1888
|
+
"POST /api/v1/cron/reconcile-overpayment-refunds": {
|
|
1889
|
+
"status": "excluded",
|
|
1890
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1891
|
+
},
|
|
1868
1892
|
"GET /api/v1/cron/reconcile-reply-capture": {
|
|
1869
1893
|
"status": "excluded",
|
|
1870
1894
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -2623,6 +2647,10 @@
|
|
|
2623
2647
|
"status": "planned",
|
|
2624
2648
|
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2625
2649
|
},
|
|
2650
|
+
"POST /api/v1/email/opt-out-list/resubscribe": {
|
|
2651
|
+
"status": "planned",
|
|
2652
|
+
"reason": "bulk email send surfaces"
|
|
2653
|
+
},
|
|
2626
2654
|
"POST /api/v1/email/send": {
|
|
2627
2655
|
"status": "covered",
|
|
2628
2656
|
"tools": [
|