conduyt-mcp 4.15.0 → 4.17.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
|
-
##
|
|
5
|
+
## 173 Tools
|
|
6
6
|
|
|
7
7
|
### Discovery
|
|
8
8
|
| Tool | Description |
|
|
@@ -270,6 +270,14 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
270
270
|
| `conduyt_update_intake_deals` | Configure automatic deal creation on live intake (webhooks, forms, bookings) |
|
|
271
271
|
| `conduyt_update_master_statuses` | Set the account's custom lead lifecycle statuses |
|
|
272
272
|
|
|
273
|
+
### Reply Capture (inbound email)
|
|
274
|
+
| Tool | Description |
|
|
275
|
+
|------|-------------|
|
|
276
|
+
| `conduyt_reply_capture_get` | Current state: configured/live, capture domain + reply address, pending DNS records, provider health |
|
|
277
|
+
| `conduyt_reply_capture_setup` | Create the receiving subdomain (must be a subdomain of the verified sending domain) and return its DNS records |
|
|
278
|
+
| `conduyt_reply_capture_verify` | Check DNS and promote to live once the provider confirms inbound receiving |
|
|
279
|
+
| `conduyt_reply_capture_remove` | Remove capture (and finish an interrupted removal) — requires `confirm='REMOVE'`; replies to already-sent email are lost |
|
|
280
|
+
|
|
273
281
|
## Setup
|
|
274
282
|
|
|
275
283
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ import { registerWorkflowSandboxTools } from "./tools/workflow-sandbox.js";
|
|
|
37
37
|
import { registerPrivacyTools } from "./tools/privacy.js";
|
|
38
38
|
import { registerAccountExportTools } from "./tools/account-exports.js";
|
|
39
39
|
import { registerLifecycleTools } from "./tools/lifecycle.js";
|
|
40
|
+
import { registerReplyCaptureTools } from "./tools/reply-capture.js";
|
|
40
41
|
const apiUrl = process.env.CONDUYT_API_URL;
|
|
41
42
|
const apiKey = process.env.CONDUYT_API_KEY;
|
|
42
43
|
if (!apiUrl || !apiKey) {
|
|
@@ -96,5 +97,6 @@ registerWorkflowSandboxTools(server, client);
|
|
|
96
97
|
registerPrivacyTools(server, client);
|
|
97
98
|
registerAccountExportTools(server, client);
|
|
98
99
|
registerLifecycleTools(server, client);
|
|
100
|
+
registerReplyCaptureTools(server, client);
|
|
99
101
|
const transport = new StdioServerTransport();
|
|
100
102
|
await server.connect(transport);
|
|
@@ -45,6 +45,10 @@ export function registerAutomationTools(server, client) {
|
|
|
45
45
|
schedule: z.string().optional().describe("Updated cron schedule"),
|
|
46
46
|
scheduleTimezone: z.string().optional().describe("Updated timezone"),
|
|
47
47
|
stopOnResponse: z.boolean().optional().describe("Stop drip if contact responds"),
|
|
48
|
+
activeRunPolicy: z
|
|
49
|
+
.enum(["keep", "restart"])
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("What a NEW trigger does when the contact is already mid-sequence: 'keep' (default — skip, existing run keeps its schedule) or 'restart' (cancel the parked run and re-enroll from step 1)"),
|
|
48
52
|
}, async ({ id, ...updates }) => {
|
|
49
53
|
const result = await client.patch(`/api/v1/automations/${id}`, updates);
|
|
50
54
|
return formatResult(result);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reply capture: the dedicated receiving subdomain that lets replies to CRM
|
|
5
|
+
* emails record and thread against the contact.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle mirrors the sending domain: create -> DNS records -> verify ->
|
|
8
|
+
* live. It is a strict subdomain of the account's own VERIFIED sending
|
|
9
|
+
* domain (the API refuses anything else — that rule is the cross-tenant
|
|
10
|
+
* fence, not a formatting preference), and only ONE capture domain can be
|
|
11
|
+
* set up at a time.
|
|
12
|
+
*
|
|
13
|
+
* Outcomes that are NORMAL here, not failures to retry blindly:
|
|
14
|
+
* - 409 "already configured" — remove the current one first.
|
|
15
|
+
* - 409 "contact support to reconcile" — the row is in an operator-repair
|
|
16
|
+
* state (a provider create whose response was lost, or a stored provider
|
|
17
|
+
* id that does not resolve to this domain). Support repairs it through a
|
|
18
|
+
* super-admin, session-only endpoint that API keys deliberately cannot
|
|
19
|
+
* reach; do not loop on it.
|
|
20
|
+
* - 429 — the per-account budget (setup/teardown are limited per hour,
|
|
21
|
+
* verification per minute). DNS takes minutes to propagate; wait.
|
|
22
|
+
* - 503 — the shared email-provider allowance is busy with customer sends.
|
|
23
|
+
* Retry in a moment; it says nothing about the domain's health.
|
|
24
|
+
*
|
|
25
|
+
* Removal is IRREVERSIBLE for in-flight threads: replies to emails already
|
|
26
|
+
* sent go to an address that no longer routes, so they are lost. Hence the
|
|
27
|
+
* confirm gate below.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerReplyCaptureTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatResult } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reply capture: the dedicated receiving subdomain that lets replies to CRM
|
|
5
|
+
* emails record and thread against the contact.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle mirrors the sending domain: create -> DNS records -> verify ->
|
|
8
|
+
* live. It is a strict subdomain of the account's own VERIFIED sending
|
|
9
|
+
* domain (the API refuses anything else — that rule is the cross-tenant
|
|
10
|
+
* fence, not a formatting preference), and only ONE capture domain can be
|
|
11
|
+
* set up at a time.
|
|
12
|
+
*
|
|
13
|
+
* Outcomes that are NORMAL here, not failures to retry blindly:
|
|
14
|
+
* - 409 "already configured" — remove the current one first.
|
|
15
|
+
* - 409 "contact support to reconcile" — the row is in an operator-repair
|
|
16
|
+
* state (a provider create whose response was lost, or a stored provider
|
|
17
|
+
* id that does not resolve to this domain). Support repairs it through a
|
|
18
|
+
* super-admin, session-only endpoint that API keys deliberately cannot
|
|
19
|
+
* reach; do not loop on it.
|
|
20
|
+
* - 429 — the per-account budget (setup/teardown are limited per hour,
|
|
21
|
+
* verification per minute). DNS takes minutes to propagate; wait.
|
|
22
|
+
* - 503 — the shared email-provider allowance is busy with customer sends.
|
|
23
|
+
* Retry in a moment; it says nothing about the domain's health.
|
|
24
|
+
*
|
|
25
|
+
* Removal is IRREVERSIBLE for in-flight threads: replies to emails already
|
|
26
|
+
* sent go to an address that no longer routes, so they are lost. Hence the
|
|
27
|
+
* confirm gate below.
|
|
28
|
+
*/
|
|
29
|
+
export function registerReplyCaptureTools(server, client) {
|
|
30
|
+
server.tool("conduyt_reply_capture_get", "Get the account's reply-capture state: whether it is configured and live, the capture domain and reply address, DNS records for a pending setup, and provider health. health='checking' with a healthWarning means the provider is re-checking DNS — capture stays active. degraded=true means the provider can no longer receive on that domain and it must be removed and set up again. needsSupport=true means an operator must reconcile it first. cleanupPending=true means a previous removal did not finish at the provider — call conduyt_reply_capture_remove again to complete it.", {}, async () => {
|
|
31
|
+
const result = await client.get("/api/v1/email-domains/reply-capture");
|
|
32
|
+
return formatResult(result);
|
|
33
|
+
});
|
|
34
|
+
server.tool("conduyt_reply_capture_setup", "Start reply-capture setup: creates the receiving domain at the email provider and returns the DNS records to add. The domain MUST be a strict subdomain of the account's own verified sending domain (omit it to use the suggested 'reply.<sending-domain>'); anything else is refused. Requires a verified sending domain first. After adding the DNS records, call conduyt_reply_capture_verify. A 409 means capture is already configured, a setup is already in progress, or the previous one still needs operator reconciliation.", {
|
|
35
|
+
domain: z
|
|
36
|
+
.string()
|
|
37
|
+
// TRIM BEFORE the emptiness check: the API trims and treats a blank
|
|
38
|
+
// as "omitted", silently creating reply.<sending-domain>. An empty
|
|
39
|
+
// agent variable would then burn mutation quota, take the account's
|
|
40
|
+
// ONE capture slot, and block sending-domain removal. Fail here.
|
|
41
|
+
.trim()
|
|
42
|
+
.min(1, "domain must not be blank — omit it entirely to use the suggested default")
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Capture subdomain, e.g. 'reply.acme.com'. Must be a subdomain of the account's verified sending domain. Omit to use the suggested default."),
|
|
45
|
+
}, async ({ domain }) => {
|
|
46
|
+
// An absent domain is serialized away, which is exactly how the API
|
|
47
|
+
// reads "use the suggested reply.<sending-domain>".
|
|
48
|
+
const result = await client.post("/api/v1/email-domains/reply-capture", { domain });
|
|
49
|
+
return formatResult(result);
|
|
50
|
+
});
|
|
51
|
+
server.tool("conduyt_reply_capture_verify", "Check the pending reply-capture domain's DNS and promote it to live once the provider reports it verified with inbound receiving in place. Returns verified=false plus the current DNS record states while records are still propagating — that is expected, not an error; poll sparingly (DNS takes minutes, and this endpoint is budgeted per account). Promotion is what turns on inbound matching and the Reply-To on outgoing email.", {}, async () => {
|
|
52
|
+
const result = await client.post("/api/v1/email-domains/reply-capture/verify", {});
|
|
53
|
+
return formatResult(result);
|
|
54
|
+
});
|
|
55
|
+
server.tool("conduyt_reply_capture_remove", "Remove reply capture: stops using the capture Reply-To on new email, stops matching inbound replies, and deletes the receiving domain at the provider. Replies to emails ALREADY SENT will be lost — there is no undo. Also completes a previously interrupted removal (cleanupPending) without creating anything new. Requires an explicit confirm='REMOVE'. Note: while any reply-capture row exists the sending domain cannot be removed, so this is also the step that unblocks that.", {
|
|
56
|
+
confirm: z
|
|
57
|
+
.literal("REMOVE")
|
|
58
|
+
.describe("Must be exactly 'REMOVE'. Hard safety gate: replies to already-sent emails are irrecoverably lost once the capture domain is gone."),
|
|
59
|
+
}, async () => {
|
|
60
|
+
const result = await client.del("/api/v1/email-domains/reply-capture");
|
|
61
|
+
return formatResult(result);
|
|
62
|
+
});
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conduyt-mcp",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "MCP server for Conduyt CRM
|
|
3
|
+
"version": "4.17.0",
|
|
4
|
+
"description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -57,4 +57,4 @@
|
|
|
57
57
|
"tsx": "^4.19.0",
|
|
58
58
|
"@types/node": "^22.0.0"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
{
|
|
204
204
|
"match": "/api/v1/email-domains",
|
|
205
205
|
"status": "planned",
|
|
206
|
-
"reason": "sending-domain management"
|
|
206
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered \u2014 see the reply_capture_* tools)"
|
|
207
207
|
},
|
|
208
208
|
{
|
|
209
209
|
"match": "/api/v1/mailbox",
|
|
@@ -501,4 +501,4 @@
|
|
|
501
501
|
"reason": "outbound webhook subscription management (distinct from the inbound /webhooks/ receivers)"
|
|
502
502
|
}
|
|
503
503
|
]
|
|
504
|
-
}
|
|
504
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-08-06T22:46:04.536Z",
|
|
3
|
+
"toolCount": 173,
|
|
4
|
+
"totalEndpoints": 842,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
6
|
+
"covered": 161,
|
|
7
|
+
"excluded": 248,
|
|
8
|
+
"planned": 433,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -297,6 +297,14 @@
|
|
|
297
297
|
"status": "excluded",
|
|
298
298
|
"reason": "super-admin platform operations (Conduyt staff only)"
|
|
299
299
|
},
|
|
300
|
+
"GET /api/v1/admin/reply-capture/reconcile": {
|
|
301
|
+
"status": "excluded",
|
|
302
|
+
"reason": "super-admin platform operations (Conduyt staff only)"
|
|
303
|
+
},
|
|
304
|
+
"POST /api/v1/admin/reply-capture/reconcile": {
|
|
305
|
+
"status": "excluded",
|
|
306
|
+
"reason": "super-admin platform operations (Conduyt staff only)"
|
|
307
|
+
},
|
|
300
308
|
"GET /api/v1/admin/scale-readiness": {
|
|
301
309
|
"status": "excluded",
|
|
302
310
|
"reason": "super-admin platform operations (Conduyt staff only)"
|
|
@@ -1289,6 +1297,10 @@
|
|
|
1289
1297
|
"status": "planned",
|
|
1290
1298
|
"reason": "conversation thread surfaces"
|
|
1291
1299
|
},
|
|
1300
|
+
"POST /api/v1/conversations/:contactId/compliance": {
|
|
1301
|
+
"status": "planned",
|
|
1302
|
+
"reason": "conversation thread surfaces"
|
|
1303
|
+
},
|
|
1292
1304
|
"GET /api/v1/conversations/:contactId/state": {
|
|
1293
1305
|
"status": "planned",
|
|
1294
1306
|
"reason": "conversation thread surfaces"
|
|
@@ -1321,6 +1333,10 @@
|
|
|
1321
1333
|
"status": "excluded",
|
|
1322
1334
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1323
1335
|
},
|
|
1336
|
+
"GET /api/v1/cron/capture-rfc-ids": {
|
|
1337
|
+
"status": "excluded",
|
|
1338
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1339
|
+
},
|
|
1324
1340
|
"GET /api/v1/cron/cleanup-audit-logs": {
|
|
1325
1341
|
"status": "excluded",
|
|
1326
1342
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -1373,6 +1389,10 @@
|
|
|
1373
1389
|
"status": "excluded",
|
|
1374
1390
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1375
1391
|
},
|
|
1392
|
+
"GET /api/v1/cron/process-consent-outbox": {
|
|
1393
|
+
"status": "excluded",
|
|
1394
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1395
|
+
},
|
|
1376
1396
|
"GET /api/v1/cron/process-data-model-jobs": {
|
|
1377
1397
|
"status": "excluded",
|
|
1378
1398
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -1477,6 +1497,10 @@
|
|
|
1477
1497
|
"status": "excluded",
|
|
1478
1498
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1479
1499
|
},
|
|
1500
|
+
"GET /api/v1/cron/reconcile-reply-capture": {
|
|
1501
|
+
"status": "excluded",
|
|
1502
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1503
|
+
},
|
|
1480
1504
|
"GET /api/v1/cron/recurring-tasks": {
|
|
1481
1505
|
"status": "excluded",
|
|
1482
1506
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -2033,27 +2057,51 @@
|
|
|
2033
2057
|
},
|
|
2034
2058
|
"GET /api/v1/email-domains": {
|
|
2035
2059
|
"status": "planned",
|
|
2036
|
-
"reason": "sending-domain management"
|
|
2060
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2037
2061
|
},
|
|
2038
2062
|
"POST /api/v1/email-domains": {
|
|
2039
2063
|
"status": "planned",
|
|
2040
|
-
"reason": "sending-domain management"
|
|
2064
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2041
2065
|
},
|
|
2042
2066
|
"PATCH /api/v1/email-domains": {
|
|
2043
2067
|
"status": "planned",
|
|
2044
|
-
"reason": "sending-domain management"
|
|
2068
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2045
2069
|
},
|
|
2046
2070
|
"DELETE /api/v1/email-domains": {
|
|
2047
2071
|
"status": "planned",
|
|
2048
|
-
"reason": "sending-domain management"
|
|
2072
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2073
|
+
},
|
|
2074
|
+
"GET /api/v1/email-domains/reply-capture": {
|
|
2075
|
+
"status": "covered",
|
|
2076
|
+
"tools": [
|
|
2077
|
+
"conduyt_reply_capture_get"
|
|
2078
|
+
]
|
|
2079
|
+
},
|
|
2080
|
+
"POST /api/v1/email-domains/reply-capture": {
|
|
2081
|
+
"status": "covered",
|
|
2082
|
+
"tools": [
|
|
2083
|
+
"conduyt_reply_capture_setup"
|
|
2084
|
+
]
|
|
2085
|
+
},
|
|
2086
|
+
"DELETE /api/v1/email-domains/reply-capture": {
|
|
2087
|
+
"status": "covered",
|
|
2088
|
+
"tools": [
|
|
2089
|
+
"conduyt_reply_capture_remove"
|
|
2090
|
+
]
|
|
2091
|
+
},
|
|
2092
|
+
"POST /api/v1/email-domains/reply-capture/verify": {
|
|
2093
|
+
"status": "covered",
|
|
2094
|
+
"tools": [
|
|
2095
|
+
"conduyt_reply_capture_verify"
|
|
2096
|
+
]
|
|
2049
2097
|
},
|
|
2050
2098
|
"GET /api/v1/email-domains/status": {
|
|
2051
2099
|
"status": "planned",
|
|
2052
|
-
"reason": "sending-domain management"
|
|
2100
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2053
2101
|
},
|
|
2054
2102
|
"POST /api/v1/email-domains/verify": {
|
|
2055
2103
|
"status": "planned",
|
|
2056
|
-
"reason": "sending-domain management"
|
|
2104
|
+
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2057
2105
|
},
|
|
2058
2106
|
"POST /api/v1/email/send": {
|
|
2059
2107
|
"status": "covered",
|
|
@@ -2455,6 +2503,10 @@
|
|
|
2455
2503
|
"status": "planned",
|
|
2456
2504
|
"reason": "message surfaces beyond send tools"
|
|
2457
2505
|
},
|
|
2506
|
+
"GET /api/v1/messages/:id/media/:mediaIndex": {
|
|
2507
|
+
"status": "planned",
|
|
2508
|
+
"reason": "message surfaces beyond send tools"
|
|
2509
|
+
},
|
|
2458
2510
|
"GET /api/v1/messages/sms/:id": {
|
|
2459
2511
|
"status": "planned",
|
|
2460
2512
|
"reason": "message surfaces beyond send tools"
|