conduyt-mcp 4.51.0 → 4.53.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/dist/index.js +2 -0
- package/dist/tools/fromk.d.ts +10 -0
- package/dist/tools/fromk.js +45 -0
- package/dist/tools/messaging.js +3 -2
- package/dist/tools/smart-views.js +6 -1
- package/package.json +1 -1
- package/scripts/coverage-matrix.json +53 -5
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ 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
44
|
import { registerWhatsappTools } from "./tools/whatsapp.js";
|
|
45
|
+
import { registerFromkTools } from "./tools/fromk.js";
|
|
45
46
|
import { registerReportTools } from "./tools/reports.js";
|
|
46
47
|
import { registerReportDashboardTools } from "./tools/dashboards.js";
|
|
47
48
|
const apiUrl = process.env.CONDUYT_API_URL;
|
|
@@ -108,6 +109,7 @@ registerReplyCaptureTools(server, client);
|
|
|
108
109
|
registerCallFlowTools(server, client);
|
|
109
110
|
registerProjectBlueTools(server, client);
|
|
110
111
|
registerWhatsappTools(server, client);
|
|
112
|
+
registerFromkTools(server, client);
|
|
111
113
|
registerReportTools(server, client);
|
|
112
114
|
registerReportDashboardTools(server, client);
|
|
113
115
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* FromK.ai (2026-09-14): Conduyt's own texting platform as a native SMS transport. The tenant's FromK.ai workspace
|
|
5
|
+
* texts (its lines, its credits, its consent and STOP rules); Conduyt drives it with the workspace's API key. These
|
|
6
|
+
* tools cover what an AI operator needs: is it connected, which lines are send-ready (the fromkPhoneNumberId a step or
|
|
7
|
+
* conduyt_send_sms picks), refreshing that list, connecting or replacing the key, storing the webhook signing secret,
|
|
8
|
+
* and disconnecting.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerFromkTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatResult } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* FromK.ai (2026-09-14): Conduyt's own texting platform as a native SMS transport. The tenant's FromK.ai workspace
|
|
5
|
+
* texts (its lines, its credits, its consent and STOP rules); Conduyt drives it with the workspace's API key. These
|
|
6
|
+
* tools cover what an AI operator needs: is it connected, which lines are send-ready (the fromkPhoneNumberId a step or
|
|
7
|
+
* conduyt_send_sms picks), refreshing that list, connecting or replacing the key, storing the webhook signing secret,
|
|
8
|
+
* and disconnecting.
|
|
9
|
+
*/
|
|
10
|
+
export function registerFromkTools(server, client) {
|
|
11
|
+
server.tool("conduyt_fromk_status", "FromK.ai connection for this account (GET /api/v1/settings/fromk): status connected | not_configured, the API key masked, the lines snapshot (phoneNumberId, phoneE164, status, provider, sendReady) and the webhook setup (the URL the tenant pastes into FromK.ai → Developers → Webhooks, whether the signing secret is stored, the events to subscribe). Requires settings:view.", {}, async () => {
|
|
12
|
+
const result = await client.get("/api/v1/settings/fromk");
|
|
13
|
+
return formatResult(result);
|
|
14
|
+
});
|
|
15
|
+
server.tool("conduyt_fromk_lines", "Refresh the FromK.ai line list from the workspace with the stored key and store it as the new snapshot (GET /api/v1/settings/fromk/lines): every line with sendReady (assigned, ownership verified, SMS-capable, on a real carrier). A send-ready phoneNumberId is a valid send_sms config.fromkPhoneNumberId / conduyt_send_sms fromkPhoneNumberId. 409 fromk_not_configured, 422 key_revoked, 502 vendor_unavailable (the old snapshot is kept). Requires settings:view.", {}, async () => {
|
|
16
|
+
const result = await client.get("/api/v1/settings/fromk/lines");
|
|
17
|
+
return formatResult(result);
|
|
18
|
+
});
|
|
19
|
+
server.tool("conduyt_fromk_connect", "Connect or replace the account's FromK.ai workspace API key (PUT /api/v1/settings/fromk): fk_… with numbers:read, contacts:write and messages:send. Verified with FromK.ai before it is stored (it must list the workspace's lines: 422 key_rejected / key_scope, 502 vendor_unavailable, nothing stored). Pass disconnect: true to forget the key, the signing secret, the lines and every contact link instead (DELETE): workflow steps on the FromK.ai channel stop sending. Requires settings:edit.", {
|
|
20
|
+
apiKey: z.string().min(24).max(200).optional().describe("FromK.ai workspace API key (fk_…); required unless disconnect is true"),
|
|
21
|
+
disconnect: z.boolean().optional().describe("true = forget everything FromK.ai (DELETE)"),
|
|
22
|
+
}, async ({ apiKey, disconnect }) => {
|
|
23
|
+
if (disconnect) {
|
|
24
|
+
const result = await client.del("/api/v1/settings/fromk");
|
|
25
|
+
return formatResult(result);
|
|
26
|
+
}
|
|
27
|
+
if (!apiKey)
|
|
28
|
+
throw new Error("apiKey is required unless disconnect is true");
|
|
29
|
+
const result = await client.put("/api/v1/settings/fromk", { apiKey });
|
|
30
|
+
return formatResult(result);
|
|
31
|
+
});
|
|
32
|
+
server.tool("conduyt_fromk_webhook_secret", "Store (PUT /api/v1/settings/fromk/webhook) or forget (DELETE, clear: true) the signing secret of the FromK.ai webhook endpoint the tenant pointed at Conduyt (whsec_…, shown once by FromK.ai when the endpoint is created). Until it is stored, FromK.ai replies and delivery reports are refused. 409 fromk_not_configured before the key is connected. Requires settings:edit.", {
|
|
33
|
+
secret: z.string().regex(/^whsec_[A-Za-z0-9_-]{16,}$/).optional().describe("the endpoint's signing secret; required unless clear is true"),
|
|
34
|
+
clear: z.boolean().optional().describe("true = forget the stored secret (DELETE)"),
|
|
35
|
+
}, async ({ secret, clear }) => {
|
|
36
|
+
if (clear) {
|
|
37
|
+
const result = await client.del("/api/v1/settings/fromk/webhook");
|
|
38
|
+
return formatResult(result);
|
|
39
|
+
}
|
|
40
|
+
if (!secret)
|
|
41
|
+
throw new Error("secret is required unless clear is true");
|
|
42
|
+
const result = await client.put("/api/v1/settings/fromk/webhook", { secret });
|
|
43
|
+
return formatResult(result);
|
|
44
|
+
});
|
|
45
|
+
}
|
package/dist/tools/messaging.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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}, 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
|
|
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 connected | setup, connected, lines[] = the FromK.ai workspace's send-ready lines, each phoneNumberId a valid config.fromkPhoneNumberId for smsMode fromk}. 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
|
});
|
|
@@ -22,7 +22,8 @@ export function registerMessagingTools(server, client) {
|
|
|
22
22
|
contactId: z.string().describe("Contact UUID — must have a phone number on file"),
|
|
23
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."),
|
|
24
24
|
fromNumber: z.string().optional().describe("Sender phone number (defaults to account's primary number)"),
|
|
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."),
|
|
25
|
+
transport: z.enum(["twilio", "project_blue", "whatsapp", "fromk"]).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. 'fromk' sends through the account's FromK.ai workspace (Settings → FromK.ai) from fromkPhoneNumberId, else the workspace's single send-ready line (422 fromk_line_required with several). Default: Twilio."),
|
|
26
|
+
fromkPhoneNumberId: z.string().uuid().optional().describe("transport fromk: the FromK.ai line to send from (a send-ready phoneNumberId from conduyt_fromk_status / conduyt_sms_transports fromk.lines); omit when the workspace has one send-ready line"),
|
|
26
27
|
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
28
|
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\" }"),
|
|
28
29
|
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."),
|
|
@@ -32,13 +32,17 @@ export function registerSmartViewTools(server, client) {
|
|
|
32
32
|
.optional()
|
|
33
33
|
.describe("Most calls to one lead in a single account-local day (1–20); null removes the daily cap"),
|
|
34
34
|
dialCooldownMinutes: z.number().int().min(0).max(10080).optional().describe("Minutes to wait between calls to the same lead"),
|
|
35
|
+
dialIncludeUnassigned: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("true = this priority also hands out leads nobody owns yet (a shared pool; the lead is assigned to the rep at hand-out); false = the rep's own leads only"),
|
|
35
39
|
dialSchedule: z
|
|
36
40
|
.array(scheduleStepSchema)
|
|
37
41
|
.min(1)
|
|
38
42
|
.max(20)
|
|
39
43
|
.optional()
|
|
40
44
|
.describe('Retry schedule, e.g. [{"attemptNumber":1,"delayMinutes":0},{"attemptNumber":2,"delayMinutes":60},{"attemptNumber":3,"delayMinutes":1440}]'),
|
|
41
|
-
}, async ({ id, dialEnabled, maxDialAttempts, maxDialAttemptsPerDay, dialCooldownMinutes, dialSchedule }) => {
|
|
45
|
+
}, async ({ id, dialEnabled, maxDialAttempts, maxDialAttemptsPerDay, dialCooldownMinutes, dialIncludeUnassigned, dialSchedule }) => {
|
|
42
46
|
// Inline body (undefined fields drop out in JSON) — keeps the shape
|
|
43
47
|
// readable by the contract verifier's static parser.
|
|
44
48
|
const result = await client.patch(`/api/v1/smart-views/${encodeURIComponent(id)}`, {
|
|
@@ -46,6 +50,7 @@ export function registerSmartViewTools(server, client) {
|
|
|
46
50
|
maxDialAttempts,
|
|
47
51
|
maxDialAttemptsPerDay,
|
|
48
52
|
dialCooldownMinutes,
|
|
53
|
+
dialIncludeUnassigned,
|
|
49
54
|
dialSchedule,
|
|
50
55
|
});
|
|
51
56
|
return formatResult(result);
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-09-14T20:41:35.427Z",
|
|
3
|
+
"toolCount": 260,
|
|
4
|
+
"totalEndpoints": 974,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
6
|
+
"covered": 245,
|
|
7
|
+
"excluded": 288,
|
|
8
8
|
"planned": 441,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
@@ -2239,6 +2239,14 @@
|
|
|
2239
2239
|
"status": "excluded",
|
|
2240
2240
|
"reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
|
|
2241
2241
|
},
|
|
2242
|
+
"GET /api/v1/dialer/queue/open": {
|
|
2243
|
+
"status": "excluded",
|
|
2244
|
+
"reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
|
|
2245
|
+
},
|
|
2246
|
+
"POST /api/v1/dialer/queue/release": {
|
|
2247
|
+
"status": "excluded",
|
|
2248
|
+
"reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
|
|
2249
|
+
},
|
|
2242
2250
|
"POST /api/v1/dialer/queue/skip": {
|
|
2243
2251
|
"status": "excluded",
|
|
2244
2252
|
"reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
|
|
@@ -3623,6 +3631,42 @@
|
|
|
3623
3631
|
"status": "planned",
|
|
3624
3632
|
"reason": "account settings read/update surfaces"
|
|
3625
3633
|
},
|
|
3634
|
+
"GET /api/v1/settings/fromk": {
|
|
3635
|
+
"status": "covered",
|
|
3636
|
+
"tools": [
|
|
3637
|
+
"conduyt_fromk_status"
|
|
3638
|
+
]
|
|
3639
|
+
},
|
|
3640
|
+
"PUT /api/v1/settings/fromk": {
|
|
3641
|
+
"status": "covered",
|
|
3642
|
+
"tools": [
|
|
3643
|
+
"conduyt_fromk_connect"
|
|
3644
|
+
]
|
|
3645
|
+
},
|
|
3646
|
+
"DELETE /api/v1/settings/fromk": {
|
|
3647
|
+
"status": "covered",
|
|
3648
|
+
"tools": [
|
|
3649
|
+
"conduyt_fromk_connect"
|
|
3650
|
+
]
|
|
3651
|
+
},
|
|
3652
|
+
"GET /api/v1/settings/fromk/lines": {
|
|
3653
|
+
"status": "covered",
|
|
3654
|
+
"tools": [
|
|
3655
|
+
"conduyt_fromk_lines"
|
|
3656
|
+
]
|
|
3657
|
+
},
|
|
3658
|
+
"PUT /api/v1/settings/fromk/webhook": {
|
|
3659
|
+
"status": "covered",
|
|
3660
|
+
"tools": [
|
|
3661
|
+
"conduyt_fromk_webhook_secret"
|
|
3662
|
+
]
|
|
3663
|
+
},
|
|
3664
|
+
"DELETE /api/v1/settings/fromk/webhook": {
|
|
3665
|
+
"status": "covered",
|
|
3666
|
+
"tools": [
|
|
3667
|
+
"conduyt_fromk_webhook_secret"
|
|
3668
|
+
]
|
|
3669
|
+
},
|
|
3626
3670
|
"POST /api/v1/settings/integrations/:id/test": {
|
|
3627
3671
|
"status": "planned",
|
|
3628
3672
|
"reason": "account settings read/update surfaces"
|
|
@@ -4246,6 +4290,10 @@
|
|
|
4246
4290
|
"status": "planned",
|
|
4247
4291
|
"reason": "outbound webhook delivery replay — agent-relevant"
|
|
4248
4292
|
},
|
|
4293
|
+
"POST /api/v1/webhooks/sms/fromk/:accountId": {
|
|
4294
|
+
"status": "excluded",
|
|
4295
|
+
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
4296
|
+
},
|
|
4249
4297
|
"POST /api/v1/webhooks/sms/inbound": {
|
|
4250
4298
|
"status": "excluded",
|
|
4251
4299
|
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|