conduyt-mcp 4.49.0 → 4.51.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 +4 -2
- package/dist/index.js +2 -0
- package/dist/tools/messaging.js +18 -3
- package/dist/tools/whatsapp.d.ts +8 -0
- package/dist/tools/whatsapp.js +30 -0
- package/package.json +1 -1
- package/scripts/coverage-matrix.json +45 -5
package/README.md
CHANGED
|
@@ -86,8 +86,10 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
86
86
|
### Messaging
|
|
87
87
|
| Tool | Description |
|
|
88
88
|
|------|-------------|
|
|
89
|
-
| `conduyt_sms_transports` | What a workflow Send SMS step can speak through today: Twilio numbers, Project Blue lines held by active reps, webhook provider count, FromK.ai coming soon |
|
|
90
|
-
| `
|
|
89
|
+
| `conduyt_sms_transports` | What a workflow Send SMS step can speak through today: Twilio numbers, Project Blue lines held by active reps, the WhatsApp sender, the Voicemail Drop recordings, webhook provider count, FromK.ai coming soon |
|
|
90
|
+
| `conduyt_voice_drop` | Place a voice drop to a contact: an outbound Twilio call with answering-machine detection that plays a Voicemail Drop recording to a machine (hangs up on a person unless onHuman is play); governed like a call (calls-tier consent, calling hours, one live call per lead) |
|
|
91
|
+
| `conduyt_whatsapp_templates` | The account's WhatsApp templates (Twilio Content) with Meta's approval verdict: sid, text with {{n}} placeholders, variables, approval |
|
|
92
|
+
| `conduyt_send_sms` | Send SMS to a contact (max 1600 chars); transport twilio, project_blue, or whatsapp (an approved template by Content SID, or free-form within the 24-hour window) |
|
|
91
93
|
| `conduyt_send_email` | Send a single email to a contact |
|
|
92
94
|
|
|
93
95
|
### Companies
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ import { registerLifecycleTools } from "./tools/lifecycle.js";
|
|
|
41
41
|
import { registerReplyCaptureTools } from "./tools/reply-capture.js";
|
|
42
42
|
import { registerCallFlowTools } from "./tools/call-flows.js";
|
|
43
43
|
import { registerProjectBlueTools } from "./tools/project-blue.js";
|
|
44
|
+
import { registerWhatsappTools } from "./tools/whatsapp.js";
|
|
44
45
|
import { registerReportTools } from "./tools/reports.js";
|
|
45
46
|
import { registerReportDashboardTools } from "./tools/dashboards.js";
|
|
46
47
|
const apiUrl = process.env.CONDUYT_API_URL;
|
|
@@ -106,6 +107,7 @@ registerLifecycleTools(server, client);
|
|
|
106
107
|
registerReplyCaptureTools(server, client);
|
|
107
108
|
registerCallFlowTools(server, client);
|
|
108
109
|
registerProjectBlueTools(server, client);
|
|
110
|
+
registerWhatsappTools(server, client);
|
|
109
111
|
registerReportTools(server, client);
|
|
110
112
|
registerReportDashboardTools(server, client);
|
|
111
113
|
const transport = new StdioServerTransport();
|
package/dist/tools/messaging.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult } from "../client.js";
|
|
3
3
|
export function registerMessagingTools(server, client) {
|
|
4
|
-
server.tool("conduyt_sms_transports", "What a workflow Send SMS step can speak through TODAY (GET /api/v1/sms/transports): twilio {connected, numbers[] = the account number + every active rep's assigned line, each a valid send_sms config.fromNumber}, projectBlue {connected, lines[] = Project Blue iPhone lines held by active reps, each lineId a valid config.projectBlueLineId for smsMode project_blue}, webhook {providers = active webhook providers for smsMode webhook}, fromk {status: coming_soon}. Use it before building or editing a send_sms step so you never configure a transport that is not connected. Requires automations:view.", {}, async () => {
|
|
4
|
+
server.tool("conduyt_sms_transports", "What a workflow Send SMS step can speak through TODAY (GET /api/v1/sms/transports): twilio {connected, numbers[] = the account number + every active rep's assigned line, each a valid send_sms config.fromNumber}, projectBlue {connected, lines[] = Project Blue iPhone lines held by active reps, each lineId a valid config.projectBlueLineId for smsMode project_blue}, whatsapp {connected, sender = the account's WhatsApp sender for smsMode whatsapp; templates come from conduyt_whatsapp_templates}, voiceDrop {connected = Twilio is connected, recordings {hasDefault, templates[] = names valid as config.voiceDropRecording for smsMode voice_drop}}, webhook {providers = active webhook providers for smsMode webhook}, fromk {status: coming_soon}. Use it before building or editing a send_sms step so you never configure a transport that is not connected. Requires automations:view.", {}, async () => {
|
|
5
5
|
const result = await client.get("/api/v1/sms/transports");
|
|
6
6
|
return formatResult(result);
|
|
7
7
|
});
|
|
8
|
+
server.tool("conduyt_whatsapp_templates", "The account's WhatsApp templates (Twilio Content) with Meta's approval verdict (GET /api/v1/sms/whatsapp/templates): sid (a send_sms step's whatsappTemplateSid / conduyt_send_sms whatsappContentSid), friendlyName, language, body (the approved text with {{1}}, {{2}} placeholders), variables (the placeholder numbers to fill), approval (approved | pending | rejected | paused | disabled | unsubmitted | unknown), rejectionReason, category; plus `connected` (Twilio credentials AND a WhatsApp sender stored). Only an APPROVED template can be sent; outreach outside a contact's 24-hour window must use one. Reads Twilio live. Requires automations:view.", {}, async () => {
|
|
9
|
+
const result = await client.get("/api/v1/sms/whatsapp/templates");
|
|
10
|
+
return formatResult(result);
|
|
11
|
+
});
|
|
12
|
+
server.tool("conduyt_voice_drop", "Place a voice drop to a contact (POST /api/v1/calls/voice-drop): an outbound Twilio call with answering-machine detection that plays one of the account's Voicemail Drop recordings (Settings → Twilio → Voicemail Drop; names from conduyt_sms_transports voiceDrop.recordings) when a machine answers, and hangs up quietly when a person answers unless onHuman is play. Governed like a call: calls-tier consent (a texting STOP does not block it, a DNC or litigator row does), the account's calling hours, one live call per lead. Placed from your own Twilio line (else the account number) unless fromNumber names a number the account owns. Returns 202 with the Call row; the outcome lands on it through the voice webhooks (disposition voicemail_drop = played to a machine, voice_drop_human = a person answered, voice_drop_live = played to a person, voice_drop_fax; status no-answer/busy when nobody picked up). Refusals carry a code (dnc, litigator, calling_window, active_call, missing_phone, voice_drop_recording_missing, voice_drop_not_configured); 429 and 502 are safe to retry. Requires dialer:create.", {
|
|
13
|
+
contactId: z.string().uuid().describe("Contact UUID — the drop calls the contact's primary number"),
|
|
14
|
+
recording: z.string().min(1).max(100).optional().describe("A Voicemail Drop template name (see conduyt_sms_transports voiceDrop.recordings.templates); omit for the default recording"),
|
|
15
|
+
fromNumber: z.string().regex(/^\+[1-9]\d{6,14}$/).optional().describe("Caller ID the account owns (the account number or an active rep's line, E.164); omit for your own line"),
|
|
16
|
+
onHuman: z.enum(["hang_up", "play"]).optional().describe("When a person answers: hang_up (default) ends the call quietly; play plays the recording anyway"),
|
|
17
|
+
}, async (params) => {
|
|
18
|
+
const result = await client.post("/api/v1/calls/voice-drop", params);
|
|
19
|
+
return formatResult(result);
|
|
20
|
+
});
|
|
8
21
|
server.tool("conduyt_send_sms", "Send an SMS message to a contact. Contact must have a phone number and not have opted out. Max 1600 characters.", {
|
|
9
22
|
contactId: z.string().describe("Contact UUID — must have a phone number on file"),
|
|
10
|
-
body: z.string().describe("
|
|
23
|
+
body: z.string().max(1600).optional().describe("Message text (max 1600 characters). Required unless a WhatsApp template is sent (transport whatsapp + whatsappContentSid): then the template Twilio renders is the text, and a media-only template has none."),
|
|
11
24
|
fromNumber: z.string().optional().describe("Sender phone number (defaults to account's primary number)"),
|
|
12
|
-
transport: z.enum(["twilio", "project_blue"]).optional().describe("'project_blue' sends from the acting user's own Project Blue iPhone line (iMessage when the lead is on Apple, SMS otherwise; the user must have a line assigned in Settings → Project Blue). Default: Twilio."),
|
|
25
|
+
transport: z.enum(["twilio", "project_blue", "whatsapp"]).optional().describe("'project_blue' sends from the acting user's own Project Blue iPhone line (iMessage when the lead is on Apple, SMS otherwise; the user must have a line assigned in Settings → Project Blue). 'whatsapp' sends from the account's WhatsApp sender (Settings → WhatsApp): free-form text only within 24 hours of the contact's last WhatsApp message (422 whatsapp_window_closed otherwise), or an approved template via whatsappContentSid. Default: Twilio."),
|
|
26
|
+
whatsappContentSid: z.string().regex(/^HX[0-9a-fA-F]{32}$/).optional().describe("transport whatsapp: an APPROVED WhatsApp template (Twilio Content SID from conduyt_whatsapp_templates); Conduyt reads the template from Twilio at send time, records its text with the variables in place, and refuses an unlisted (whatsapp_template_invalid) or unapproved (whatsapp_template_not_approved) one. body is still required but the template's text is what is sent and recorded."),
|
|
27
|
+
whatsappContentVariables: z.record(z.string().regex(/^\d+$/), z.string().max(1024)).optional().describe("transport whatsapp with a template: placeholder number → value, e.g. { \"1\": \"Alex\" }"),
|
|
13
28
|
idempotencyKey: z.string().min(8).max(200).optional().describe("Operation key for at-most-once sends: reuse the SAME key when retrying a send whose outcome you did not see; a settled key answers with the original message and never sends twice."),
|
|
14
29
|
}, async (params) => {
|
|
15
30
|
const result = await client.post("/api/v1/messages/sms/send", params);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* WhatsApp via Twilio (2026-09-11): the tenant's own Twilio account carries a Meta-approved WhatsApp sender. These
|
|
5
|
+
* tools cover what an AI operator needs: is WhatsApp connected and from which number, which senders Twilio lists
|
|
6
|
+
* for the account (only an ONLINE one can be connected), connect / disconnect. Templates: conduyt_whatsapp_templates.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerWhatsappTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatResult } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* WhatsApp via Twilio (2026-09-11): the tenant's own Twilio account carries a Meta-approved WhatsApp sender. These
|
|
5
|
+
* tools cover what an AI operator needs: is WhatsApp connected and from which number, which senders Twilio lists
|
|
6
|
+
* for the account (only an ONLINE one can be connected), connect / disconnect. Templates: conduyt_whatsapp_templates.
|
|
7
|
+
*/
|
|
8
|
+
export function registerWhatsappTools(server, client) {
|
|
9
|
+
server.tool("conduyt_whatsapp_status", "WhatsApp connection for this account (GET /api/v1/settings/whatsapp): status connected | not_configured | twilio_required (a sender is stored but Twilio is disconnected), twilioConnected, and the sender {phoneNumber (plain E.164), sid}. WhatsApp rides the account's Twilio credentials; the workflow Send SMS step, conduyt_send_sms (transport whatsapp) and the contact composer can use it once connected. Requires settings:view.", {}, async () => {
|
|
10
|
+
const result = await client.get("/api/v1/settings/whatsapp");
|
|
11
|
+
return formatResult(result);
|
|
12
|
+
});
|
|
13
|
+
server.tool("conduyt_whatsapp_senders", "The WhatsApp senders Twilio lists under this account's credentials (GET /api/v1/settings/whatsapp/senders): sid (XE…), phoneNumber, status (only ONLINE can be connected), displayName, connected (the stored one). 422 twilio_required without Twilio credentials. Requires settings:view.", {}, async () => {
|
|
14
|
+
const result = await client.get("/api/v1/settings/whatsapp/senders");
|
|
15
|
+
return formatResult(result);
|
|
16
|
+
});
|
|
17
|
+
server.tool("conduyt_whatsapp_connect", "Connect the account's WhatsApp sender (PUT /api/v1/settings/whatsapp): pass the sender sid from conduyt_whatsapp_senders. Verified with Twilio before it is stored: it must be on this account and ONLINE (422 sender_not_found / sender_not_online), one sender belongs to one Conduyt account (409 sender_in_use), Twilio is required (422 twilio_required). Pass disconnect: true to forget the sender instead (DELETE): workflow steps on the WhatsApp channel stop sending. Requires settings:edit.", {
|
|
18
|
+
senderSid: z.string().regex(/^XE[0-9a-fA-F]{32}$/).optional().describe("Twilio WhatsApp sender SID (XE…) from conduyt_whatsapp_senders; required unless disconnect is true"),
|
|
19
|
+
disconnect: z.boolean().optional().describe("true = forget the stored sender (DELETE)"),
|
|
20
|
+
}, async ({ senderSid, disconnect }) => {
|
|
21
|
+
if (disconnect) {
|
|
22
|
+
const result = await client.del("/api/v1/settings/whatsapp");
|
|
23
|
+
return formatResult(result);
|
|
24
|
+
}
|
|
25
|
+
if (!senderSid)
|
|
26
|
+
throw new Error("senderSid is required unless disconnect is true");
|
|
27
|
+
const result = await client.put("/api/v1/settings/whatsapp", { senderSid });
|
|
28
|
+
return formatResult(result);
|
|
29
|
+
});
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-09-11T22:38:09.066Z",
|
|
3
|
+
"toolCount": 256,
|
|
4
|
+
"totalEndpoints": 965,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
6
|
+
"covered": 239,
|
|
7
|
+
"excluded": 285,
|
|
8
8
|
"planned": 441,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
@@ -1154,6 +1154,12 @@
|
|
|
1154
1154
|
"status": "planned",
|
|
1155
1155
|
"reason": "call subroutes beyond list/log"
|
|
1156
1156
|
},
|
|
1157
|
+
"POST /api/v1/calls/voice-drop": {
|
|
1158
|
+
"status": "covered",
|
|
1159
|
+
"tools": [
|
|
1160
|
+
"conduyt_voice_drop"
|
|
1161
|
+
]
|
|
1162
|
+
},
|
|
1157
1163
|
"GET /api/v1/chat/channels": {
|
|
1158
1164
|
"status": "planned",
|
|
1159
1165
|
"reason": "team chat read/post for agents"
|
|
@@ -3737,6 +3743,30 @@
|
|
|
3737
3743
|
"status": "planned",
|
|
3738
3744
|
"reason": "account settings read/update surfaces"
|
|
3739
3745
|
},
|
|
3746
|
+
"GET /api/v1/settings/whatsapp": {
|
|
3747
|
+
"status": "covered",
|
|
3748
|
+
"tools": [
|
|
3749
|
+
"conduyt_whatsapp_status"
|
|
3750
|
+
]
|
|
3751
|
+
},
|
|
3752
|
+
"PUT /api/v1/settings/whatsapp": {
|
|
3753
|
+
"status": "covered",
|
|
3754
|
+
"tools": [
|
|
3755
|
+
"conduyt_whatsapp_connect"
|
|
3756
|
+
]
|
|
3757
|
+
},
|
|
3758
|
+
"DELETE /api/v1/settings/whatsapp": {
|
|
3759
|
+
"status": "covered",
|
|
3760
|
+
"tools": [
|
|
3761
|
+
"conduyt_whatsapp_connect"
|
|
3762
|
+
]
|
|
3763
|
+
},
|
|
3764
|
+
"GET /api/v1/settings/whatsapp/senders": {
|
|
3765
|
+
"status": "covered",
|
|
3766
|
+
"tools": [
|
|
3767
|
+
"conduyt_whatsapp_senders"
|
|
3768
|
+
]
|
|
3769
|
+
},
|
|
3740
3770
|
"GET /api/v1/smart-lists": {
|
|
3741
3771
|
"status": "covered",
|
|
3742
3772
|
"tools": [
|
|
@@ -3855,6 +3885,12 @@
|
|
|
3855
3885
|
"conduyt_sms_transports"
|
|
3856
3886
|
]
|
|
3857
3887
|
},
|
|
3888
|
+
"GET /api/v1/sms/whatsapp/templates": {
|
|
3889
|
+
"status": "covered",
|
|
3890
|
+
"tools": [
|
|
3891
|
+
"conduyt_whatsapp_templates"
|
|
3892
|
+
]
|
|
3893
|
+
},
|
|
3858
3894
|
"GET /api/v1/sso/connection": {
|
|
3859
3895
|
"status": "excluded",
|
|
3860
3896
|
"reason": "SAML/SSO configuration + protocol endpoints; interactive/IdP-facing"
|
|
@@ -4270,6 +4306,10 @@
|
|
|
4270
4306
|
"status": "excluded",
|
|
4271
4307
|
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
4272
4308
|
},
|
|
4309
|
+
"POST /api/v1/webhooks/voice/voice-drop": {
|
|
4310
|
+
"status": "excluded",
|
|
4311
|
+
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
4312
|
+
},
|
|
4273
4313
|
"POST /api/v1/webhooks/voice/voicemail": {
|
|
4274
4314
|
"status": "excluded",
|
|
4275
4315
|
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|