conduyt-mcp 4.87.0 → 4.89.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/tools/contacts.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ export declare const MERGE_TIMEOUT_MS = 75000;
|
|
|
9
9
|
export declare const LOSER_ORDER_NOTE = "losers are merged in the order given: for each field the survivor lacks, the first loser in this order with a value wins; reorder to change precedence; the dry run's filledFields shows which fields will be filled";
|
|
10
10
|
export declare const MERGE_PREVIEW_MAX = 50;
|
|
11
11
|
export declare const MERGE_PREVIEW_TTL_MS: number;
|
|
12
|
+
export declare const EMAIL_LOOKUP_NOTE = "Exact email lookup: one address, or up to 20 distinct addresses in an array. Each is trimmed and lower-cased and matches the contact's primary email OR any secondary email slot (email_2 and up); it is an exact match, not a substring search (use `search` for partial text). Combines with every other filter (AND) inside your account and visibility. An empty or invalid address, or more than 20 distinct addresses, is a 400. On a workspace whose identifier index is still being built, the lookup answers 422 when the stored spellings of the requested addresses exceed its stored-spelling bind budget (a count of spellings, not of contacts; other filters do not help); ask support to complete the identifier backfill. Send addresses once; duplicates collapse.";
|
|
12
13
|
export declare function registerContactTools(server: McpServer, client: ConduytClient): void;
|
package/dist/tools/contacts.js
CHANGED
|
@@ -52,10 +52,14 @@ function createMergePreviewStore() {
|
|
|
52
52
|
drop(key) { rows.delete(key); },
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
/** (API ?email=, app main 318adf38d) exact lookup of one address; the API trims and lower-cases, so a stray space is harmless */
|
|
56
|
+
const EMAIL_LOOKUP_ONE = z.string().trim().min(1).max(254);
|
|
57
|
+
export const EMAIL_LOOKUP_NOTE = "Exact email lookup: one address, or up to 20 distinct addresses in an array. Each is trimmed and lower-cased and matches the contact's primary email OR any secondary email slot (email_2 and up); it is an exact match, not a substring search (use `search` for partial text). Combines with every other filter (AND) inside your account and visibility. An empty or invalid address, or more than 20 distinct addresses, is a 400. On a workspace whose identifier index is still being built, the lookup answers 422 when the stored spellings of the requested addresses exceed its stored-spelling bind budget (a count of spellings, not of contacts; other filters do not help); ask support to complete the identifier backfill. Send addresses once; duplicates collapse.";
|
|
55
58
|
export function registerContactTools(server, client) {
|
|
56
59
|
const mergePreviews = createMergePreviewStore();
|
|
57
|
-
server.tool("conduyt_list_contacts", "List and search contacts with filtering. Returns paginated results with name, email, phone, tags, source, and assignee. Each row carries `revision` (integer save revision; pass it back as expectedRevision on conduyt_update_contact or the collaborator tools).", {
|
|
60
|
+
server.tool("conduyt_list_contacts", "List and search contacts with filtering. To find the contact(s) holding a known email address, pass `email` (exact match on the primary or any secondary email); `search` is a substring search. Returns paginated results with name, email, phone, tags, source, and assignee. Each row carries `revision` (integer save revision; pass it back as expectedRevision on conduyt_update_contact or the collaborator tools).", {
|
|
58
61
|
search: z.string().optional().describe("Search by name, email, or phone number"),
|
|
62
|
+
email: z.union([EMAIL_LOOKUP_ONE, z.array(EMAIL_LOOKUP_ONE).min(1).max(20)]).optional().describe(EMAIL_LOOKUP_NOTE),
|
|
59
63
|
tag: z.string().optional().describe("Filter by tag name"),
|
|
60
64
|
source: z.string().optional().describe("Filter by source (e.g. 'signup', 'import', 'manual')"),
|
|
61
65
|
assignedTo: z.string().optional().describe("Filter by assigned user ID"),
|
|
@@ -63,10 +67,13 @@ export function registerContactTools(server, client) {
|
|
|
63
67
|
excludeMasterStatus: z.array(z.string().trim().min(1).max(40)).max(20).optional().describe("Hide contacts whose lead status is one of these — e.g. ['won','lost','abandoned','disqualified'] is the 'Active' view. Naming a status in both lists is a 400."),
|
|
64
68
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
65
69
|
per_page: z.number().optional().describe("Results per page (default 25, max 100)"),
|
|
66
|
-
}, async ({ search, tag, source, assignedTo, masterStatus, excludeMasterStatus, page, per_page }) => {
|
|
70
|
+
}, async ({ search, email, tag, source, assignedTo, masterStatus, excludeMasterStatus, page, per_page }) => {
|
|
67
71
|
const params = new URLSearchParams();
|
|
68
72
|
if (search)
|
|
69
73
|
params.set("search", search);
|
|
74
|
+
// repeated `email` params, one per address (the API reads every occurrence); an omitted or empty array sends none
|
|
75
|
+
for (const address of email === undefined ? [] : Array.isArray(email) ? email : [email])
|
|
76
|
+
params.append("email", address);
|
|
70
77
|
if (tag)
|
|
71
78
|
params.set("tag", tag);
|
|
72
79
|
if (source)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { ConduytClient } from "../client.js";
|
|
3
4
|
type DisclosureOn = "inbound" | "outbound" | "both";
|
|
4
5
|
type RecordingPolicy = {
|
|
@@ -20,6 +21,8 @@ type RecordingPolicy = {
|
|
|
20
21
|
export declare function effectiveRecordingPolicy(raw: unknown): RecordingPolicy | null;
|
|
21
22
|
/** the iPhone line has no inbound calls: what its policy does there, the way the server applies it */
|
|
22
23
|
export declare function iphoneDomain(policy: RecordingPolicy): RecordingPolicy;
|
|
24
|
+
/** 0 = the wrap-up auto-release is off, otherwise 30..3600 seconds (the API refuses anything else with 400) */
|
|
25
|
+
export declare const wrapupAutoReleaseSecondsSchema: z.ZodEffects<z.ZodNumber, number, number>;
|
|
23
26
|
/**
|
|
24
27
|
* Smart Dialing policy (GET/PATCH /api/v1/dialer/settings): the account-wide rules every rep's Smart Dialing run
|
|
25
28
|
* follows. Read needs the dialer scope; update needs the settings scope (admin). (#95/#96, 2026-09-16)
|
|
@@ -29,24 +29,27 @@ export function iphoneDomain(policy) {
|
|
|
29
29
|
const on = policy.disclosure.on;
|
|
30
30
|
return { ...policy, inbound: false, disclosure: on === "inbound" ? { enabled: false, on: "outbound" } : { enabled: policy.disclosure.enabled, on: "outbound" } };
|
|
31
31
|
}
|
|
32
|
+
/** 0 = the wrap-up auto-release is off, otherwise 30..3600 seconds (the API refuses anything else with 400) */
|
|
33
|
+
export const wrapupAutoReleaseSecondsSchema = z.number().int().refine((n) => n === 0 || (n >= 30 && n <= 3600), "wrapupAutoReleaseSeconds must be 0 (off) or a whole number of seconds from 30 to 3600");
|
|
32
34
|
/**
|
|
33
35
|
* Smart Dialing policy (GET/PATCH /api/v1/dialer/settings): the account-wide rules every rep's Smart Dialing run
|
|
34
36
|
* follows. Read needs the dialer scope; update needs the settings scope (admin). (#95/#96, 2026-09-16)
|
|
35
37
|
*/
|
|
36
38
|
export function registerDialerSettingsTools(server, client) {
|
|
37
|
-
server.tool("conduyt_get_dialer_settings", "Read the account's Smart Dialing policy: autoAdvanceEnabled + pauseSeconds + advanceOn ('disposition' = the next dial waits for a saved disposition, 'timer' = the pause starts when the call ends), openLeadOnDial (the lead's record opens as each Smart Dialing call goes out), smartDialCallerId ('local_presence' = an area-code match from the number pool, the rep's own line when no pool exists; 'did' = always the rep's own line), and localPresenceConfigured (whether a number pool exists).", {}, async () => formatResult(await client.get("/api/v1/dialer/settings")));
|
|
38
|
-
server.tool("conduyt_update_dialer_settings", "Change the account's Smart Dialing policy (admin: settings scope). Send only the fields to change: autoAdvanceEnabled, pauseSeconds (1-60), advanceOn ('disposition' | 'timer'), openLeadOnDial (true = the lead's record opens as each Smart Dialing call goes out), smartDialCallerId ('local_presence' | 'did'). Returns the policy as saved.", {
|
|
39
|
+
server.tool("conduyt_get_dialer_settings", "Read the account's Smart Dialing policy: autoAdvanceEnabled + pauseSeconds + advanceOn ('disposition' = the next dial waits for a saved disposition, 'timer' = the pause starts when the call ends), openLeadOnDial (the lead's record opens as each Smart Dialing call goes out), smartDialCallerId ('local_presence' = an area-code match from the number pool, the rep's own line when no pool exists; 'did' = always the rep's own line), wrapupAutoReleaseSeconds (seconds of continuous wrap-up after which a rep returns to Available automatically: 0 = off, otherwise 30 to 3600; default 300), and localPresenceConfigured (whether a number pool exists).", {}, async () => formatResult(await client.get("/api/v1/dialer/settings")));
|
|
40
|
+
server.tool("conduyt_update_dialer_settings", "Change the account's Smart Dialing policy (admin: settings scope). Send only the fields to change: autoAdvanceEnabled, pauseSeconds (1-60), advanceOn ('disposition' | 'timer'), openLeadOnDial (true = the lead's record opens as each Smart Dialing call goes out), smartDialCallerId ('local_presence' | 'did'), wrapupAutoReleaseSeconds (0 = off, otherwise 30 to 3600: seconds of continuous wrap-up after which a rep returns to Available automatically). Returns the policy as saved.", {
|
|
39
41
|
autoAdvanceEnabled: z.boolean().optional().describe("Start the next dial automatically after a call"),
|
|
40
42
|
pauseSeconds: z.number().int().min(1).max(60).optional().describe("Seconds between calls when auto-advance is on"),
|
|
41
43
|
advanceOn: z.enum(["disposition", "timer"]).optional().describe("'disposition': wait for a saved disposition; 'timer': the pause starts when the call ends"),
|
|
42
44
|
openLeadOnDial: z.boolean().optional().describe("Open the lead's record as each Smart Dialing call goes out (default true)"),
|
|
43
45
|
smartDialCallerId: z.enum(["local_presence", "did"]).optional().describe("Line Smart Dialing calls go out on: Local Presence (default) or the rep's own DID"),
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
wrapupAutoReleaseSeconds: wrapupAutoReleaseSecondsSchema.optional().describe("Seconds of continuous wrap-up after which a rep returns to Available automatically: 0 = off, otherwise 30 to 3600 (default 300)"),
|
|
47
|
+
}, async ({ autoAdvanceEnabled, pauseSeconds, advanceOn, openLeadOnDial, smartDialCallerId, wrapupAutoReleaseSeconds }) => {
|
|
48
|
+
if ([autoAdvanceEnabled, pauseSeconds, advanceOn, openLeadOnDial, smartDialCallerId, wrapupAutoReleaseSeconds].every((v) => v === undefined)) {
|
|
49
|
+
return { content: [{ type: "text", text: "Nothing to change: pass at least one of autoAdvanceEnabled, pauseSeconds, advanceOn, openLeadOnDial, smartDialCallerId, wrapupAutoReleaseSeconds." }], isError: true };
|
|
47
50
|
}
|
|
48
51
|
// undefined fields drop out of the JSON body, so only the named changes reach the route
|
|
49
|
-
const result = await client.patch("/api/v1/dialer/settings", { autoAdvanceEnabled, pauseSeconds, advanceOn, openLeadOnDial, smartDialCallerId });
|
|
52
|
+
const result = await client.patch("/api/v1/dialer/settings", { autoAdvanceEnabled, pauseSeconds, advanceOn, openLeadOnDial, smartDialCallerId, wrapupAutoReleaseSeconds });
|
|
50
53
|
return formatResult(result);
|
|
51
54
|
});
|
|
52
55
|
// Call recording policy (the Call recording card of Settings, released 2026-09-30): which calls are recorded and
|
package/package.json
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"status": "planned",
|
|
11
11
|
"reason": "single-function read; the list tool plus update/delete/test exist, a typed get is wanted but not built (4.23.0)"
|
|
12
12
|
},
|
|
13
|
+
{
|
|
14
|
+
"match": "POST /api/v1/feature-interest",
|
|
15
|
+
"status": "excluded",
|
|
16
|
+
"reason": "session-only UI endpoint, not an API-key capability (the signed-in user's interest click on an unreleased feature, e.g. the Gmail inbox; no API-key access)"
|
|
17
|
+
},
|
|
13
18
|
{
|
|
14
19
|
"match": "/api/health",
|
|
15
20
|
"status": "excluded",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-10-
|
|
2
|
+
"generatedAt": "2026-10-15T15:19:50.604Z",
|
|
3
3
|
"toolCount": 350,
|
|
4
|
-
"totalEndpoints":
|
|
4
|
+
"totalEndpoints": 1085,
|
|
5
5
|
"counts": {
|
|
6
6
|
"covered": 309,
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
7
|
+
"excluded": 324,
|
|
8
|
+
"planned": 452,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -1941,6 +1941,10 @@
|
|
|
1941
1941
|
"status": "excluded",
|
|
1942
1942
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1943
1943
|
},
|
|
1944
|
+
"GET /api/v1/cron/recover-attachment-ledgers": {
|
|
1945
|
+
"status": "excluded",
|
|
1946
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1947
|
+
},
|
|
1944
1948
|
"GET /api/v1/cron/recover-booking-side-effects": {
|
|
1945
1949
|
"status": "excluded",
|
|
1946
1950
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -1957,6 +1961,10 @@
|
|
|
1957
1961
|
"status": "excluded",
|
|
1958
1962
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1959
1963
|
},
|
|
1964
|
+
"GET /api/v1/cron/release-google-grants": {
|
|
1965
|
+
"status": "excluded",
|
|
1966
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1967
|
+
},
|
|
1960
1968
|
"GET /api/v1/cron/reply-capture-drain": {
|
|
1961
1969
|
"status": "excluded",
|
|
1962
1970
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -2826,6 +2834,10 @@
|
|
|
2826
2834
|
"status": "planned",
|
|
2827
2835
|
"reason": "client error ingestion"
|
|
2828
2836
|
},
|
|
2837
|
+
"POST /api/v1/feature-interest": {
|
|
2838
|
+
"status": "excluded",
|
|
2839
|
+
"reason": "session-only UI endpoint, not an API-key capability (the signed-in user's interest click on an unreleased feature, e.g. the Gmail inbox; no API-key access)"
|
|
2840
|
+
},
|
|
2829
2841
|
"GET /api/v1/files": {
|
|
2830
2842
|
"status": "planned",
|
|
2831
2843
|
"reason": "file listing/metadata is agent-useful; upload stays browser-side"
|
|
@@ -3227,10 +3239,18 @@
|
|
|
3227
3239
|
"status": "planned",
|
|
3228
3240
|
"reason": "lead routing rule management"
|
|
3229
3241
|
},
|
|
3242
|
+
"GET /api/v1/mailbox/callback/google": {
|
|
3243
|
+
"status": "planned",
|
|
3244
|
+
"reason": "mailbox sync surfaces"
|
|
3245
|
+
},
|
|
3230
3246
|
"GET /api/v1/mailbox/callback/microsoft": {
|
|
3231
3247
|
"status": "planned",
|
|
3232
3248
|
"reason": "mailbox sync surfaces"
|
|
3233
3249
|
},
|
|
3250
|
+
"POST /api/v1/mailbox/connect/google": {
|
|
3251
|
+
"status": "planned",
|
|
3252
|
+
"reason": "mailbox sync surfaces"
|
|
3253
|
+
},
|
|
3234
3254
|
"POST /api/v1/mailbox/connect/microsoft": {
|
|
3235
3255
|
"status": "planned",
|
|
3236
3256
|
"reason": "mailbox sync surfaces"
|