conduyt-mcp 4.21.0 → 4.22.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/client.d.ts
CHANGED
|
@@ -49,6 +49,9 @@ export declare class ConduytClient {
|
|
|
49
49
|
request(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
50
50
|
requestText(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<string>;
|
|
51
51
|
get(path: string, opts?: RequestOptions): Promise<unknown>;
|
|
52
|
+
/** Multipart upload (call-flow greeting audio): same auth, timeouts and
|
|
53
|
+
* response bounds as JSON calls; the body is a FormData the caller built. */
|
|
54
|
+
postMultipart(path: string, form: FormData, opts?: RequestOptions): Promise<unknown>;
|
|
52
55
|
post(path: string, body: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
53
56
|
postText(path: string, body: unknown, opts?: RequestOptions): Promise<string>;
|
|
54
57
|
patch(path: string, body: unknown, opts?: RequestOptions): Promise<unknown>;
|
package/dist/client.js
CHANGED
|
@@ -235,6 +235,32 @@ export class ConduytClient {
|
|
|
235
235
|
get(path, opts) {
|
|
236
236
|
return this.request("GET", path, undefined, opts);
|
|
237
237
|
}
|
|
238
|
+
/** Multipart upload (call-flow greeting audio): same auth, timeouts and
|
|
239
|
+
* response bounds as JSON calls; the body is a FormData the caller built. */
|
|
240
|
+
async postMultipart(path, form, opts = {}) {
|
|
241
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
242
|
+
const url = `${this.baseUrl}${path}`;
|
|
243
|
+
let res;
|
|
244
|
+
try {
|
|
245
|
+
res = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}` }, body: form, signal: AbortSignal.timeout(timeoutMs) });
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
throw new ConduytApiError({ message: `Conduyt API network error for POST ${path}: ${err?.message ?? String(err)}`, code: err?.name === "TimeoutError" ? "timeout" : "network", retryable: true, cause: err });
|
|
249
|
+
}
|
|
250
|
+
const text = await res.text();
|
|
251
|
+
let json = null;
|
|
252
|
+
try {
|
|
253
|
+
json = text ? JSON.parse(text) : null;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
json = { raw: text };
|
|
257
|
+
}
|
|
258
|
+
if (!res.ok) {
|
|
259
|
+
const msg = json?.error ?? text.slice(0, 300);
|
|
260
|
+
throw new ConduytApiError({ message: `Conduyt API ${res.status}: ${msg}`, status: res.status, code: "http", retryable: res.status >= 500 });
|
|
261
|
+
}
|
|
262
|
+
return json;
|
|
263
|
+
}
|
|
238
264
|
post(path, body, opts) {
|
|
239
265
|
return this.request("POST", path, body, opts);
|
|
240
266
|
}
|
|
@@ -30,7 +30,10 @@ export function registerAutomationTools(server, client) {
|
|
|
30
30
|
graphVersion: z.number().optional().describe("Graph version — 1 (legacy array) or 2 (node graph). Default 1"),
|
|
31
31
|
schedule: z.string().optional().describe("5-field cron expression (required if triggerEvent='scheduled')"),
|
|
32
32
|
scheduleTimezone: z.string().optional().describe("IANA timezone for scheduled triggers (e.g. 'America/Los_Angeles')"),
|
|
33
|
-
triggerConditions: z
|
|
33
|
+
triggerConditions: z
|
|
34
|
+
.any()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Conditions that must match for the trigger to fire. Scalar filters like tagId/pipelineId/bookingPageIds, plus per-event config: for 'contact.untouched' pass { untouchedDays: N } (integer 1-365) to fire once per quiet episode when a contact has had no outreach for N+ days — without it the trigger is the minutes-scale speed-to-lead alert. Unknown keys on native events are rejected at create/publish (422)."),
|
|
34
37
|
}, async (params) => {
|
|
35
38
|
const result = await client.post("/api/v1/automations", params);
|
|
36
39
|
return formatResult(result);
|
|
@@ -40,6 +43,10 @@ export function registerAutomationTools(server, client) {
|
|
|
40
43
|
name: z.string().optional().describe("Updated name"),
|
|
41
44
|
description: z.string().optional().describe("Updated description"),
|
|
42
45
|
triggerEvent: z.string().optional().describe("Updated trigger event"),
|
|
46
|
+
triggerConditions: z
|
|
47
|
+
.any()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Updated trigger conditions (replaces the object). Same keys as create — e.g. { untouchedDays: 7 } on 'contact.untouched'. (#16 tri-surface parity: the UI and API could set this; MCP could not.)"),
|
|
43
50
|
isActive: z.boolean().optional().describe("Enable or disable the automation"),
|
|
44
51
|
actions: z.any().optional().describe("Updated actions graph"),
|
|
45
52
|
schedule: z.string().optional().describe("Updated cron schedule"),
|
package/dist/tools/call-flows.js
CHANGED
|
@@ -32,7 +32,7 @@ export function registerCallFlowTools(server, client) {
|
|
|
32
32
|
server.tool("conduyt_create_call_flow", "Create a call flow. Starts as a DRAFT — edit the graph, validate, then publish to make it live on its number.", {
|
|
33
33
|
name: z.string().describe("Flow name"),
|
|
34
34
|
phoneNumber: z.string().optional().describe("E.164 number this flow answers (must belong to the account)"),
|
|
35
|
-
draftGraph: flowGraphSchema.optional().describe("Initial flow graph — MUST have a string entryId and a nodes array (anything else is silently replaced by a default voicemail flow server-side)"),
|
|
35
|
+
draftGraph: flowGraphSchema.optional().describe("Initial flow graph — MUST have a string entryId and a nodes array (anything else is silently replaced by a default voicemail flow server-side). Node kinds: entry, greeting, agent, group (queue: holdSeconds/pressOneVoicemail/repeatRing = queue mode), menu (options[{digit,label,next}], next = no-choice path), timing (hours[{day,start,end}], timezone, next = during hours, closedNext = outside hours), voicemail, forward, hangup. Every branch must reach an ending — publish refuses otherwise."),
|
|
36
36
|
}, async (params) => {
|
|
37
37
|
const result = await client.post("/api/v1/call-flows", params);
|
|
38
38
|
return formatResult(result);
|
|
@@ -41,7 +41,7 @@ export function registerCallFlowTools(server, client) {
|
|
|
41
41
|
id: z.string().describe("Call flow UUID"),
|
|
42
42
|
name: z.string().optional().describe("New name"),
|
|
43
43
|
phoneNumber: z.string().optional().describe("New E.164 number"),
|
|
44
|
-
draftGraph: flowGraphSchema.optional().describe("Replacement draft graph — MUST have a string entryId and a nodes array; REQUIRES expectedDraftRevision"),
|
|
44
|
+
draftGraph: flowGraphSchema.optional().describe("Replacement draft graph — MUST have a string entryId and a nodes array; REQUIRES expectedDraftRevision. Kinds: entry, greeting, agent, group (queue.repeatRing = queue mode), menu (options[{digit,label,next}]), timing (hours, timezone, next/closedNext), voicemail, forward, hangup; every branch must reach an ending."),
|
|
45
45
|
expectedDraftRevision: z.number().int().min(0).optional().describe("draftRevision from your last conduyt_get_call_flow read (REQUIRED when replacing draftGraph)"),
|
|
46
46
|
}, async ({ id, ...updates }) => {
|
|
47
47
|
if (updates.draftGraph !== undefined && updates.expectedDraftRevision === undefined) {
|
|
@@ -139,4 +139,49 @@ export function registerCallFlowTools(server, client) {
|
|
|
139
139
|
const result = await client.post("/api/v1/ring-groups/release-number", params);
|
|
140
140
|
return formatResult(result);
|
|
141
141
|
});
|
|
142
|
+
// ── Rollout fix 2026-08-24: numbers, Twilio wiring, greeting audio ──────
|
|
143
|
+
server.tool("conduyt_list_call_flow_numbers", "The voice numbers this workspace owns in Twilio, each with what answers it in Conduyt today (claimedBy: call flow / ring group / main line) and whether its Twilio voice webhook is already wired to Conduyt. Use it to pick a free number for a flow.", {}, async () => {
|
|
144
|
+
const result = await client.get("/api/v1/call-flows/numbers");
|
|
145
|
+
return formatResult(result);
|
|
146
|
+
});
|
|
147
|
+
server.tool("conduyt_get_call_flow_wiring", "Twilio's truth for a call flow's number: the voice webhook Twilio has right now vs the inboundUrl Conduyt needs, and whether they match (wired). A published flow that is NOT wired does not receive calls.", { id: z.string().describe("Call flow UUID") }, async ({ id }) => {
|
|
148
|
+
const result = await client.get(`/api/v1/call-flows/${id}/wiring`);
|
|
149
|
+
return formatResult(result);
|
|
150
|
+
});
|
|
151
|
+
server.tool("conduyt_wire_call_flow_number", "Point the flow's number's Twilio voice webhook at Conduyt (the same step publish performs; use it when publish reported wiring.error, or after changing the number in Twilio). Admin. Returns the resulting wiring state.", { id: z.string().describe("Call flow UUID") }, async ({ id }) => {
|
|
152
|
+
const result = await client.post(`/api/v1/call-flows/${id}/wiring`, {});
|
|
153
|
+
return formatResult(result);
|
|
154
|
+
});
|
|
155
|
+
server.tool("conduyt_release_call_flow_tombstone", "Release a number still held by a deleted call flow's tombstone: restores the number's pre-Conduyt Twilio configuration (verified) and lets the tombstone go. Admin. 404 when no tombstone holds the number; 409 when Conduyt still answers it and no prior configuration is known.", { phoneNumber: z.string().describe("E.164 number held by the tombstone") }, async ({ phoneNumber }) => {
|
|
156
|
+
const result = await client.post(`/api/v1/call-flows/tombstones/release`, { phoneNumber });
|
|
157
|
+
return formatResult(result);
|
|
158
|
+
});
|
|
159
|
+
server.tool("conduyt_upload_call_flow_audio", "Upload a greeting recording (mp3, wav or m4a, up to 10 MB) for use in a greeting node's audioUrl. Pass the file as base64. Returns { url } — put it on the greeting node via conduyt_update_call_flow.", {
|
|
160
|
+
filename: z.string().describe("File name with extension, e.g. greeting.mp3"),
|
|
161
|
+
contentBase64: z.string().describe("The file bytes, base64-encoded"),
|
|
162
|
+
mimeType: z.string().optional().describe("audio/mpeg | audio/wav | audio/mp4 (inferred from the extension when omitted)"),
|
|
163
|
+
}, async ({ filename, contentBase64, mimeType }) => {
|
|
164
|
+
const ext = filename.toLowerCase().split(".").pop() ?? "";
|
|
165
|
+
const type = mimeType ?? { mp3: "audio/mpeg", wav: "audio/wav", m4a: "audio/mp4" }[ext] ?? "application/octet-stream";
|
|
166
|
+
let bytes;
|
|
167
|
+
try {
|
|
168
|
+
bytes = Buffer.from(contentBase64, "base64");
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return formatToolError("contentBase64 is not valid base64");
|
|
172
|
+
}
|
|
173
|
+
if (bytes.byteLength === 0)
|
|
174
|
+
return formatToolError("The file is empty");
|
|
175
|
+
if (bytes.byteLength > 10 * 1024 * 1024)
|
|
176
|
+
return formatToolError("The recording exceeds 10 MB");
|
|
177
|
+
const form = new FormData();
|
|
178
|
+
form.append("file", new Blob([Uint8Array.from(bytes)], { type }), filename);
|
|
179
|
+
try {
|
|
180
|
+
const result = await client.postMultipart("/api/v1/call-flows/audio", form, { timeoutMs: 60_000 });
|
|
181
|
+
return formatResult(result);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return formatToolError(err.message);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
142
187
|
}
|
package/dist/tools/contacts.js
CHANGED
|
@@ -51,6 +51,7 @@ export function registerContactTools(server, client) {
|
|
|
51
51
|
firstName: z.string().optional().describe("First name"),
|
|
52
52
|
lastName: z.string().optional().describe("Last name"),
|
|
53
53
|
phone: z.string().optional().describe("Phone number"),
|
|
54
|
+
timezone: z.string().optional().describe("IANA timezone of the lead (e.g. America/Chicago). Omit to let Conduyt derive it from ZIP → phone area code → state (the stored contact carries timezone + timezoneSource)."),
|
|
54
55
|
company: z.string().optional().describe("Company name"),
|
|
55
56
|
source: z.string().trim().min(1).max(100).optional().describe("Lead source (e.g. 'website', 'referral', 'import') — FIRST-WRITE-WINS acquisition truth for the lead; later inbound events do not overwrite it. 1-100 chars."),
|
|
56
57
|
masterStatus: z.string().trim().min(1).max(40).optional().describe("Lifecycle status of the LEAD (master record), distinct from any deal's status: 'open' (default), 'won', 'lost', 'abandoned', 'disqualified', or an account-defined custom status. A terminal status (won/lost/abandoned/disqualified) SILENCES all outbound automations for this contact — drips, sequences, and scheduled sends stop. Use conduyt_get_lifecycle_settings to see the account's configured statuses."),
|
|
@@ -69,6 +70,7 @@ export function registerContactTools(server, client) {
|
|
|
69
70
|
firstName: z.string().optional().describe("Updated first name"),
|
|
70
71
|
lastName: z.string().optional().describe("Updated last name"),
|
|
71
72
|
phone: z.string().optional().describe("Updated phone"),
|
|
73
|
+
timezone: z.string().nullable().optional().describe("IANA timezone override (e.g. America/Denver); pass null to clear the override and re-derive from ZIP → area code → state"),
|
|
72
74
|
company: z.string().optional().describe("Updated company name"),
|
|
73
75
|
source: z.string().trim().min(1).max(100).nullable().optional().describe("Correct the lead's first-touch source (normally first-write-wins and set at creation — update only to FIX bad attribution). Pass null to clear."),
|
|
74
76
|
masterStatus: z.string().trim().min(1).max(40).optional().describe("Lifecycle status of the LEAD (master record), distinct from any deal's status: 'open' (default), 'won', 'lost', 'abandoned', 'disqualified', or an account-defined custom status. A terminal status (won/lost/abandoned/disqualified) SILENCES all outbound automations for this contact — drips, sequences, and scheduled sends stop. Use conduyt_get_lifecycle_settings to see the account's configured statuses."),
|
package/package.json
CHANGED
|
@@ -509,6 +509,11 @@
|
|
|
509
509
|
"match": "POST /api/v1/call-flows/audio",
|
|
510
510
|
"status": "excluded",
|
|
511
511
|
"reason": "multipart file upload (IVR audio) - not expressible as a text MCP tool; agents reference existing audio by id from conduyt_get_call_flow"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
"match": "GET /api/v1/ops/capacity",
|
|
515
|
+
"status": "planned",
|
|
516
|
+
"reason": "operator capacity dashboard (#51, shipped 2026-09) \u2014 read-only queue/park telemetry; a typed conduyt_capacity_snapshot tool is agent-relevant for throughput questions"
|
|
512
517
|
}
|
|
513
518
|
]
|
|
514
|
-
}
|
|
519
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-09-02T06:05:57.360Z",
|
|
3
|
+
"toolCount": 194,
|
|
4
|
+
"totalEndpoints": 883,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
6
|
+
"covered": 181,
|
|
7
|
+
"excluded": 261,
|
|
8
|
+
"planned": 441,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -1044,16 +1044,40 @@
|
|
|
1044
1044
|
"conduyt_validate_call_flow"
|
|
1045
1045
|
]
|
|
1046
1046
|
},
|
|
1047
|
+
"GET /api/v1/call-flows/:id/wiring": {
|
|
1048
|
+
"status": "covered",
|
|
1049
|
+
"tools": [
|
|
1050
|
+
"conduyt_get_call_flow_wiring"
|
|
1051
|
+
]
|
|
1052
|
+
},
|
|
1053
|
+
"POST /api/v1/call-flows/:id/wiring": {
|
|
1054
|
+
"status": "covered",
|
|
1055
|
+
"tools": [
|
|
1056
|
+
"conduyt_wire_call_flow_number"
|
|
1057
|
+
]
|
|
1058
|
+
},
|
|
1047
1059
|
"POST /api/v1/call-flows/audio": {
|
|
1048
1060
|
"status": "excluded",
|
|
1049
1061
|
"reason": "multipart file upload (IVR audio) - not expressible as a text MCP tool; agents reference existing audio by id from conduyt_get_call_flow"
|
|
1050
1062
|
},
|
|
1063
|
+
"GET /api/v1/call-flows/numbers": {
|
|
1064
|
+
"status": "covered",
|
|
1065
|
+
"tools": [
|
|
1066
|
+
"conduyt_list_call_flow_numbers"
|
|
1067
|
+
]
|
|
1068
|
+
},
|
|
1051
1069
|
"GET /api/v1/call-flows/roster": {
|
|
1052
1070
|
"status": "covered",
|
|
1053
1071
|
"tools": [
|
|
1054
1072
|
"conduyt_call_flow_roster"
|
|
1055
1073
|
]
|
|
1056
1074
|
},
|
|
1075
|
+
"POST /api/v1/call-flows/tombstones/release": {
|
|
1076
|
+
"status": "covered",
|
|
1077
|
+
"tools": [
|
|
1078
|
+
"conduyt_release_call_flow_tombstone"
|
|
1079
|
+
]
|
|
1080
|
+
},
|
|
1057
1081
|
"GET /api/v1/calls": {
|
|
1058
1082
|
"status": "covered",
|
|
1059
1083
|
"tools": [
|
|
@@ -1417,6 +1441,14 @@
|
|
|
1417
1441
|
"status": "excluded",
|
|
1418
1442
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1419
1443
|
},
|
|
1444
|
+
"GET /api/v1/cron/capacity-monitor": {
|
|
1445
|
+
"status": "excluded",
|
|
1446
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1447
|
+
},
|
|
1448
|
+
"POST /api/v1/cron/capacity-monitor": {
|
|
1449
|
+
"status": "excluded",
|
|
1450
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1451
|
+
},
|
|
1420
1452
|
"GET /api/v1/cron/capture-rfc-ids": {
|
|
1421
1453
|
"status": "excluded",
|
|
1422
1454
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -1561,6 +1593,14 @@
|
|
|
1561
1593
|
"status": "excluded",
|
|
1562
1594
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1563
1595
|
},
|
|
1596
|
+
"GET /api/v1/cron/process-unattended-leads": {
|
|
1597
|
+
"status": "excluded",
|
|
1598
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1599
|
+
},
|
|
1600
|
+
"GET /api/v1/cron/process-wake-jobs": {
|
|
1601
|
+
"status": "excluded",
|
|
1602
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1603
|
+
},
|
|
1564
1604
|
"GET /api/v1/cron/prune-screen-share-sessions": {
|
|
1565
1605
|
"status": "excluded",
|
|
1566
1606
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -1597,6 +1637,10 @@
|
|
|
1597
1637
|
"status": "excluded",
|
|
1598
1638
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1599
1639
|
},
|
|
1640
|
+
"GET /api/v1/cron/reconcile-voice-releases": {
|
|
1641
|
+
"status": "excluded",
|
|
1642
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1643
|
+
},
|
|
1600
1644
|
"GET /api/v1/cron/recover-booking-side-effects": {
|
|
1601
1645
|
"status": "excluded",
|
|
1602
1646
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -1799,6 +1843,10 @@
|
|
|
1799
1843
|
"status": "planned",
|
|
1800
1844
|
"reason": "deal subroutes beyond core CRUD (history, files, links)"
|
|
1801
1845
|
},
|
|
1846
|
+
"GET /api/v1/deals/:id/handoff-files/:fileId/download": {
|
|
1847
|
+
"status": "planned",
|
|
1848
|
+
"reason": "deal subroutes beyond core CRUD (history, files, links)"
|
|
1849
|
+
},
|
|
1802
1850
|
"GET /api/v1/deals/:id/history": {
|
|
1803
1851
|
"status": "planned",
|
|
1804
1852
|
"reason": "deal subroutes beyond core CRUD (history, files, links)"
|
|
@@ -2685,6 +2733,10 @@
|
|
|
2685
2733
|
"status": "planned",
|
|
2686
2734
|
"reason": "notification surfaces"
|
|
2687
2735
|
},
|
|
2736
|
+
"GET /api/v1/ops/capacity": {
|
|
2737
|
+
"status": "planned",
|
|
2738
|
+
"reason": "operator capacity dashboard (#51, shipped 2026-09) — read-only queue/park telemetry; a typed conduyt_capacity_snapshot tool is agent-relevant for throughput questions"
|
|
2739
|
+
},
|
|
2688
2740
|
"GET /api/v1/phone-numbers": {
|
|
2689
2741
|
"status": "planned",
|
|
2690
2742
|
"reason": "phone number inventory"
|