conduyt-mcp 4.2.0 → 4.3.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 +26 -3
- package/dist/client.d.ts +2 -0
- package/dist/client.js +29 -0
- package/dist/index.js +1 -1
- package/dist/tools/activities.js +8 -5
- package/dist/tools/ai-extended.js +216 -2
- package/dist/tools/ai.js +17 -4
- package/dist/tools/automations.js +3 -1
- package/dist/tools/bulk.js +59 -0
- package/dist/tools/contacts.js +6 -6
- package/dist/tools/deals.js +37 -3
- package/dist/tools/discovery.js +7 -10
- package/dist/tools/invoices.js +3 -1
- package/dist/tools/notifications.js +1 -1
- package/dist/tools/tasks.js +30 -5
- package/dist/tools/users.js +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Conduyt MCP Server
|
|
2
2
|
|
|
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
|
|
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
|
+
## 136 Tools
|
|
6
6
|
|
|
7
7
|
### Discovery
|
|
8
8
|
| Tool | Description |
|
|
9
9
|
|------|-------------|
|
|
10
|
-
| `conduyt_api_catalog` | Discover all
|
|
10
|
+
| `conduyt_api_catalog` | Discover all 500+ API endpoints |
|
|
11
11
|
| `conduyt_check_permissions` | Introspect current API key permissions |
|
|
12
12
|
| `conduyt_search` | Global search across contacts, deals, companies, tasks |
|
|
13
13
|
|
|
@@ -27,6 +27,7 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
27
27
|
| `conduyt_get_deal` | Get deal details with history, including custom-field values |
|
|
28
28
|
| `conduyt_create_deal` | Create a deal with full attributes — currency (e.g. GBP), probability, priority, expected close date, contact/company, and structured custom-field values |
|
|
29
29
|
| `conduyt_update_deal` | Move stage, change value/currency, set probability or close date, reassign, mark won/lost, or write custom-field values |
|
|
30
|
+
| `conduyt_import_deals` | Bulk-import deals (pipeline/stage by name, contact by email) via the canonical import engine — dedupes, side-effect-free, owner/admin only |
|
|
30
31
|
|
|
31
32
|
### Tasks
|
|
32
33
|
| Tool | Description |
|
|
@@ -172,6 +173,8 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
172
173
|
| `conduyt_update_user` | Update profile, role, or permissions |
|
|
173
174
|
| `conduyt_get_user_stats` | Get performance stats (deals, tasks, activity) |
|
|
174
175
|
| `conduyt_invite_user` | Invite team member via email |
|
|
176
|
+
| `conduyt_deactivate_user` | Deactivate a user (optionally reassign their records) |
|
|
177
|
+
| `conduyt_reactivate_user` | Reactivate a deactivated user |
|
|
175
178
|
|
|
176
179
|
### Bulk Operations
|
|
177
180
|
| Tool | Description |
|
|
@@ -179,6 +182,14 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
179
182
|
| `conduyt_ai_import_contacts` | AI-powered bulk import (up to 1000, auto-tag, smart list, automation) |
|
|
180
183
|
| `conduyt_export_contacts` | Export contacts as JSON with filters |
|
|
181
184
|
| `conduyt_export_deals` | Export deals as JSON with filters |
|
|
185
|
+
| `conduyt_bulk_tag_contacts` | Add one tag to many contacts |
|
|
186
|
+
| `conduyt_bulk_untag_contacts` | Remove one tag from many contacts |
|
|
187
|
+
| `conduyt_bulk_edit_contacts` | Apply the same field changes to many contacts |
|
|
188
|
+
| `conduyt_bulk_update_contacts` | Apply per-contact field changes (each row has its own id) |
|
|
189
|
+
| `conduyt_bulk_delete_contacts` | Soft-delete many contacts |
|
|
190
|
+
| `conduyt_bulk_dnc_contacts` | Add/remove DNC or litigator mark on many contacts (compliance) |
|
|
191
|
+
| `conduyt_bulk_edit_deals` | Apply the same field changes to many deals (e.g. bulk reassign) |
|
|
192
|
+
| `conduyt_bulk_update_deals` | Apply per-deal field changes (each row has its own id) |
|
|
182
193
|
|
|
183
194
|
### Drip Campaigns
|
|
184
195
|
| Tool | Description |
|
|
@@ -228,6 +239,18 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
228
239
|
| `conduyt_ai_compose_email` | AI-generated email with context-aware tone |
|
|
229
240
|
| `conduyt_ai_enrich_contact` | AI-powered contact data enrichment |
|
|
230
241
|
| `conduyt_ai_summarize_contact` | AI summary with sentiment and next steps |
|
|
242
|
+
| `conduyt_ai_chat` | Conversational AI assistant grounded in your CRM (optionally pinned to a record) |
|
|
243
|
+
| `conduyt_ai_improve_email` | Rewrite an email draft for clarity, tone, and persuasiveness |
|
|
244
|
+
| `conduyt_ai_deal_insights` | AI risk/momentum analysis and next actions for a single deal |
|
|
245
|
+
| `conduyt_ai_conversation_intelligence` | AI summary/sentiment/objections for a transcribed call |
|
|
246
|
+
| `conduyt_ai_call_disposition` | AI-suggested disposition for a call from its transcript |
|
|
247
|
+
| `conduyt_ai_daily_brief` | AI daily brief — today's priorities for a user |
|
|
248
|
+
| `conduyt_ai_next_actions` | AI-ranked next actions for a user |
|
|
249
|
+
| `conduyt_ai_feed` | Chronological stream of AI-surfaced events and insights |
|
|
250
|
+
| `conduyt_ai_usage` | AI usage and budget report over a rolling window (owner/admin only) |
|
|
251
|
+
| `conduyt_ai_task_import_and_enroll` | AI task (preview only): bulk import + tag + enroll + smart list — execution is human-session-only |
|
|
252
|
+
| `conduyt_ai_task_segment_and_act` | AI task (preview only): segment by filter, then tag/assign/update — execution is human-session-only |
|
|
253
|
+
| `conduyt_ai_task_pipeline_cleanup` | AI task (preview only): find stale deals and tag/move/notify/report — execution is human-session-only |
|
|
231
254
|
|
|
232
255
|
## Setup
|
|
233
256
|
|
package/dist/client.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ export declare class ConduytClient {
|
|
|
4
4
|
private apiKey;
|
|
5
5
|
constructor(config: ConduytConfig);
|
|
6
6
|
request(method: string, path: string, body?: unknown): Promise<unknown>;
|
|
7
|
+
requestText(method: string, path: string, body?: unknown): Promise<string>;
|
|
7
8
|
get(path: string): Promise<unknown>;
|
|
8
9
|
post(path: string, body: unknown): Promise<unknown>;
|
|
10
|
+
postText(path: string, body: unknown): Promise<string>;
|
|
9
11
|
patch(path: string, body: unknown): Promise<unknown>;
|
|
10
12
|
del(path: string): Promise<unknown>;
|
|
11
13
|
}
|
package/dist/client.js
CHANGED
|
@@ -23,12 +23,41 @@ export class ConduytClient {
|
|
|
23
23
|
}
|
|
24
24
|
return json;
|
|
25
25
|
}
|
|
26
|
+
async requestText(method, path, body) {
|
|
27
|
+
const url = `${this.baseUrl}${path}`;
|
|
28
|
+
const headers = {
|
|
29
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
};
|
|
32
|
+
const res = await fetch(url, {
|
|
33
|
+
method,
|
|
34
|
+
headers,
|
|
35
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
36
|
+
});
|
|
37
|
+
const text = await res.text().catch(() => "");
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
let msg = res.statusText;
|
|
40
|
+
try {
|
|
41
|
+
const json = JSON.parse(text);
|
|
42
|
+
msg = json.error || msg;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
if (text)
|
|
46
|
+
msg = text;
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Conduyt API ${res.status}: ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
26
52
|
get(path) {
|
|
27
53
|
return this.request("GET", path);
|
|
28
54
|
}
|
|
29
55
|
post(path, body) {
|
|
30
56
|
return this.request("POST", path, body);
|
|
31
57
|
}
|
|
58
|
+
postText(path, body) {
|
|
59
|
+
return this.requestText("POST", path, body);
|
|
60
|
+
}
|
|
32
61
|
patch(path, body) {
|
|
33
62
|
return this.request("PATCH", path, body);
|
|
34
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ if (!apiKey.startsWith("cdy_")) {
|
|
|
45
45
|
const client = new ConduytClient({ apiUrl, apiKey });
|
|
46
46
|
const server = new McpServer({
|
|
47
47
|
name: "conduyt",
|
|
48
|
-
version: "4.
|
|
48
|
+
version: "4.3.0",
|
|
49
49
|
});
|
|
50
50
|
registerDiscoveryTools(server, client);
|
|
51
51
|
registerContactTools(server, client);
|
package/dist/tools/activities.js
CHANGED
|
@@ -2,19 +2,22 @@ import { z } from "zod";
|
|
|
2
2
|
import { formatResult } from "../client.js";
|
|
3
3
|
export function registerActivityTools(server, client) {
|
|
4
4
|
server.tool("conduyt_list_activities", "List recent activities across the CRM — calls, emails, notes, meetings, deal changes, and system events.", {
|
|
5
|
-
userId: z.string().optional().describe("Filter by user UUID who performed the activity"),
|
|
6
5
|
type: z.string().optional().describe("Filter by activity type (e.g. 'call', 'email', 'note', 'meeting', 'deal_stage_changed')"),
|
|
7
6
|
contactId: z.string().optional().describe("Filter activities for a specific contact"),
|
|
7
|
+
dealId: z.string().optional().describe("Filter activities for a specific deal"),
|
|
8
|
+
search: z.string().optional().describe("Search activity titles/descriptions"),
|
|
8
9
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
9
10
|
per_page: z.number().optional().describe("Results per page (default 25, max 100)"),
|
|
10
|
-
}, async ({
|
|
11
|
+
}, async ({ type, contactId, dealId, search, page, per_page }) => {
|
|
11
12
|
const params = new URLSearchParams();
|
|
12
|
-
if (userId)
|
|
13
|
-
params.set("userId", userId);
|
|
14
13
|
if (type)
|
|
15
14
|
params.set("type", type);
|
|
16
15
|
if (contactId)
|
|
17
|
-
params.set("
|
|
16
|
+
params.set("contact_id", contactId);
|
|
17
|
+
if (dealId)
|
|
18
|
+
params.set("deal_id", dealId);
|
|
19
|
+
if (search)
|
|
20
|
+
params.set("search", search);
|
|
18
21
|
if (page)
|
|
19
22
|
params.set("page", String(page));
|
|
20
23
|
if (per_page)
|
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult } from "../client.js";
|
|
3
|
+
function parseSseText(streamText) {
|
|
4
|
+
let reply = "";
|
|
5
|
+
let sawDone = false;
|
|
6
|
+
const errors = [];
|
|
7
|
+
for (const line of streamText.split(/\r?\n/)) {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (!trimmed.startsWith("data:"))
|
|
10
|
+
continue;
|
|
11
|
+
const payload = trimmed.slice("data:".length).trim();
|
|
12
|
+
if (!payload)
|
|
13
|
+
continue;
|
|
14
|
+
if (payload === "[DONE]") {
|
|
15
|
+
sawDone = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(payload);
|
|
20
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
21
|
+
errors.push(parsed.error.trim());
|
|
22
|
+
}
|
|
23
|
+
if (typeof parsed.text === "string") {
|
|
24
|
+
reply += parsed.text;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ignore malformed chunks; the app route does the same for provider SSE.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Fail closed: any error chunk means the response is unreliable — surface it
|
|
32
|
+
// even if some text streamed before the failure (don't return a partial reply
|
|
33
|
+
// as if it were complete).
|
|
34
|
+
if (errors.length > 0) {
|
|
35
|
+
throw new Error(`AI chat stream error: ${errors.join("; ")}`);
|
|
36
|
+
}
|
|
37
|
+
// Fail closed: a successful stream always terminates with the [DONE] sentinel.
|
|
38
|
+
// Its absence means the stream was truncated mid-flight.
|
|
39
|
+
if (!sawDone) {
|
|
40
|
+
throw new Error("AI chat stream ended without a completion marker; the response may be incomplete");
|
|
41
|
+
}
|
|
42
|
+
return reply;
|
|
43
|
+
}
|
|
3
44
|
export function registerAiExtendedTools(server, client) {
|
|
4
45
|
server.tool("conduyt_ai_compose_email", "AI-powered email composition. Generates subject, body, and HTML based on contact context, deal stage, and desired tone.", {
|
|
5
46
|
contactName: z.string().describe("Recipient's name"),
|
|
@@ -11,8 +52,8 @@ export function registerAiExtendedTools(server, client) {
|
|
|
11
52
|
contactTitle: z.string().optional().describe("Recipient's job title"),
|
|
12
53
|
dealStage: z.string().optional().describe("Current deal stage for context"),
|
|
13
54
|
dealValue: z.number().optional().describe("Deal value for context"),
|
|
14
|
-
previousMessages: z.string().optional().describe("
|
|
15
|
-
tone: z.
|
|
55
|
+
previousMessages: z.array(z.string()).optional().describe("Recent previous messages for context (max 5, 2000 chars each)"),
|
|
56
|
+
tone: z.enum(["professional", "friendly", "formal", "casual"]).optional().describe("Desired tone"),
|
|
16
57
|
senderTitle: z.string().optional().describe("Your job title"),
|
|
17
58
|
}, async (params) => {
|
|
18
59
|
const result = await client.post("/api/v1/ai/compose-email", params);
|
|
@@ -35,4 +76,177 @@ export function registerAiExtendedTools(server, client) {
|
|
|
35
76
|
const result = await client.post("/api/v1/ai/summarize-contact", params);
|
|
36
77
|
return formatResult(result);
|
|
37
78
|
});
|
|
79
|
+
server.tool("conduyt_ai_chat", "Conversational AI assistant grounded in your CRM. Send a multi-turn message thread and optionally pin it to a record (contact/deal) so the assistant has live context. Returns the assistant's reply.", {
|
|
80
|
+
messages: z
|
|
81
|
+
.array(z.object({
|
|
82
|
+
role: z.enum(["user", "assistant"]).describe("Who sent the message"),
|
|
83
|
+
content: z.string().describe("Message text"),
|
|
84
|
+
}))
|
|
85
|
+
.describe("Conversation thread, oldest first. Max 20 messages, 4000 chars each."),
|
|
86
|
+
context: z.string().optional().describe("Freeform extra context to ground the reply"),
|
|
87
|
+
section: z
|
|
88
|
+
.object({
|
|
89
|
+
type: z.enum(["contact", "deal", "conversations", "dashboard", "reports", "automation", "sequence", "custom-fields", "scoring", "general"]).describe("Page/record context type"),
|
|
90
|
+
id: z.string().optional().describe("Record UUID to pull live context from"),
|
|
91
|
+
pathname: z.string().optional().describe("UI pathname the user is viewing"),
|
|
92
|
+
})
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Pin the chat to a specific record for grounded answers"),
|
|
95
|
+
}, async (params) => {
|
|
96
|
+
const streamText = await client.postText("/api/v1/ai/chat", params);
|
|
97
|
+
const reply = parseSseText(streamText);
|
|
98
|
+
return formatResult({ reply });
|
|
99
|
+
});
|
|
100
|
+
server.tool("conduyt_ai_improve_email", "Improve an email draft. Rewrites the body (and optionally the subject) for clarity, tone, and persuasiveness. Pass instructions to steer the rewrite.", {
|
|
101
|
+
body: z.string().describe("The email body to improve"),
|
|
102
|
+
subject: z.string().describe("The current subject line"),
|
|
103
|
+
instructions: z.string().optional().describe("How to improve it, e.g. 'make it shorter and more direct'"),
|
|
104
|
+
}, async (params) => {
|
|
105
|
+
const result = await client.post("/api/v1/ai/improve-email", params);
|
|
106
|
+
return formatResult(result);
|
|
107
|
+
});
|
|
108
|
+
server.tool("conduyt_ai_deal_insights", "AI analysis of a single deal. Returns risk signals, momentum, and recommended next actions based on the deal's stage, activity, and history.", {
|
|
109
|
+
dealId: z.string().describe("Deal UUID to analyze"),
|
|
110
|
+
}, async (params) => {
|
|
111
|
+
const result = await client.post("/api/v1/ai/deal-insights", params);
|
|
112
|
+
return formatResult(result);
|
|
113
|
+
});
|
|
114
|
+
server.tool("conduyt_ai_conversation_intelligence", "AI analysis of a recorded/transcribed call. Returns summary, sentiment, topics, objections, and follow-up suggestions for the conversation.", {
|
|
115
|
+
callId: z.string().describe("Call UUID to analyze (must have a transcript)"),
|
|
116
|
+
}, async (params) => {
|
|
117
|
+
const result = await client.post("/api/v1/ai/conversation-intelligence", params);
|
|
118
|
+
return formatResult(result);
|
|
119
|
+
});
|
|
120
|
+
server.tool("conduyt_ai_call_disposition", "AI-suggested call disposition. Reads the call's transcript and details and proposes a disposition/outcome status. Returns the suggested disposition.", {
|
|
121
|
+
callId: z.string().describe("Call UUID to disposition"),
|
|
122
|
+
}, async ({ callId }) => {
|
|
123
|
+
const result = await client.post(`/api/v1/dialer/call/${callId}/ai-disposition`, {});
|
|
124
|
+
return formatResult(result);
|
|
125
|
+
});
|
|
126
|
+
server.tool("conduyt_ai_daily_brief", "AI-generated daily brief for the current user. Summarizes today's priorities — deals needing attention, overdue tasks, hot leads, and recommended focus.", {}, async () => {
|
|
127
|
+
const result = await client.get("/api/v1/ai/daily-brief");
|
|
128
|
+
return formatResult(result);
|
|
129
|
+
});
|
|
130
|
+
server.tool("conduyt_ai_next_actions", "AI-ranked next actions. Returns a prioritized list of suggested actions (replies to send, deals to advance, tasks to do) for a user.", {
|
|
131
|
+
userId: z.string().optional().describe("User UUID to generate actions for (defaults to the authenticated user)"),
|
|
132
|
+
}, async ({ userId }) => {
|
|
133
|
+
const params = new URLSearchParams();
|
|
134
|
+
if (userId)
|
|
135
|
+
params.set("userId", userId);
|
|
136
|
+
const qs = params.toString();
|
|
137
|
+
const result = await client.get(`/api/v1/ai/next-actions${qs ? `?${qs}` : ""}`);
|
|
138
|
+
return formatResult(result);
|
|
139
|
+
});
|
|
140
|
+
server.tool("conduyt_ai_feed", "AI activity feed — a chronological stream of AI-surfaced events and insights across the account.", {
|
|
141
|
+
limit: z.number().optional().describe("Max items to return (default 30)"),
|
|
142
|
+
since: z.string().optional().describe("ISO timestamp; only return items after this time"),
|
|
143
|
+
}, async ({ limit, since }) => {
|
|
144
|
+
const params = new URLSearchParams();
|
|
145
|
+
if (limit)
|
|
146
|
+
params.set("limit", String(limit));
|
|
147
|
+
if (since)
|
|
148
|
+
params.set("since", since);
|
|
149
|
+
const qs = params.toString();
|
|
150
|
+
const result = await client.get(`/api/v1/ai/feed${qs ? `?${qs}` : ""}`);
|
|
151
|
+
return formatResult(result);
|
|
152
|
+
});
|
|
153
|
+
server.tool("conduyt_ai_usage", "AI usage and budget report for the account. Returns request counts and budget consumption over a rolling window. Requires an owner/admin API key — other roles get 403.", {
|
|
154
|
+
range: z
|
|
155
|
+
.union([z.literal(7), z.literal(30), z.literal(90)])
|
|
156
|
+
.optional()
|
|
157
|
+
.describe("Reporting window in days; one of 7, 30, or 90 (default 30). Other values fall back to 30."),
|
|
158
|
+
}, async ({ range }) => {
|
|
159
|
+
const params = new URLSearchParams();
|
|
160
|
+
if (range !== undefined)
|
|
161
|
+
params.set("range", String(range));
|
|
162
|
+
const qs = params.toString();
|
|
163
|
+
const result = await client.get(`/api/v1/ai/usage${qs ? `?${qs}` : ""}`);
|
|
164
|
+
return formatResult(result);
|
|
165
|
+
});
|
|
166
|
+
server.tool("conduyt_ai_task_import_and_enroll", "AI task (PREVIEW ONLY): preview bulk-importing contacts and optionally tagging them, enrolling them in an automation, and adding them to a smart list. Returns what WOULD happen — counts, matches, and warnings — without persisting anything. Executing this is intentionally restricted to a human session in the app; agent/API-key callers can only preview.", {
|
|
167
|
+
contacts: z
|
|
168
|
+
.array(z.object({
|
|
169
|
+
firstName: z.string().optional(),
|
|
170
|
+
lastName: z.string().optional(),
|
|
171
|
+
email: z.string().optional(),
|
|
172
|
+
phone: z.string().optional(),
|
|
173
|
+
company: z.string().optional(),
|
|
174
|
+
jobTitle: z.string().optional(),
|
|
175
|
+
country: z.string().optional(),
|
|
176
|
+
source: z.string().optional(),
|
|
177
|
+
addressLine1: z.string().optional(),
|
|
178
|
+
addressLine2: z.string().optional(),
|
|
179
|
+
city: z.string().optional(),
|
|
180
|
+
state: z.string().optional(),
|
|
181
|
+
zip: z.string().optional(),
|
|
182
|
+
customFields: z.record(z.any()).optional().describe("Custom-field values keyed by field key"),
|
|
183
|
+
}))
|
|
184
|
+
.describe("Contacts to preview importing (1-500)"),
|
|
185
|
+
tagIds: z.array(z.string()).optional().describe("Tag UUIDs that would be applied to all imported contacts"),
|
|
186
|
+
automationId: z.string().optional().describe("Automation UUID imported contacts would be enrolled in"),
|
|
187
|
+
smartListId: z.string().optional().describe("Smart list UUID imported contacts would be added to"),
|
|
188
|
+
}, async (params) => {
|
|
189
|
+
const result = await client.post("/api/v1/ai/tasks", { type: "import_and_enroll", ...params, dryRun: true });
|
|
190
|
+
return formatResult(result);
|
|
191
|
+
});
|
|
192
|
+
server.tool("conduyt_ai_task_segment_and_act", "AI task (PREVIEW ONLY): preview segmenting contacts by a filter and applying one action (add/remove a tag, assign to a user, or update a field) to the whole segment. Returns the matched count and what WOULD change, without persisting. Executing this is intentionally restricted to a human session in the app; agent/API-key callers can only preview.", {
|
|
193
|
+
filter: z
|
|
194
|
+
.object({
|
|
195
|
+
tagId: z.string().optional().describe("Only contacts with this tag UUID"),
|
|
196
|
+
source: z.string().optional().describe("Only contacts from this source"),
|
|
197
|
+
createdAfter: z.string().optional().describe("ISO timestamp lower bound"),
|
|
198
|
+
createdBefore: z.string().optional().describe("ISO timestamp upper bound"),
|
|
199
|
+
hasEmail: z.boolean().optional(),
|
|
200
|
+
hasPhone: z.boolean().optional(),
|
|
201
|
+
})
|
|
202
|
+
.describe("Segment selection criteria"),
|
|
203
|
+
action: z
|
|
204
|
+
.discriminatedUnion("type", [
|
|
205
|
+
z.object({
|
|
206
|
+
type: z.literal("add_tag"),
|
|
207
|
+
tagId: z.string().describe("Tag UUID to add"),
|
|
208
|
+
}),
|
|
209
|
+
z.object({
|
|
210
|
+
type: z.literal("remove_tag"),
|
|
211
|
+
tagId: z.string().describe("Tag UUID to remove"),
|
|
212
|
+
}),
|
|
213
|
+
z.object({
|
|
214
|
+
type: z.literal("assign_to"),
|
|
215
|
+
userId: z.string().describe("User UUID to assign contacts to"),
|
|
216
|
+
}),
|
|
217
|
+
z.object({
|
|
218
|
+
type: z.literal("update_field"),
|
|
219
|
+
field: z.enum([
|
|
220
|
+
"firstName",
|
|
221
|
+
"lastName",
|
|
222
|
+
"email",
|
|
223
|
+
"phone",
|
|
224
|
+
"company",
|
|
225
|
+
"jobTitle",
|
|
226
|
+
"source",
|
|
227
|
+
"city",
|
|
228
|
+
"state",
|
|
229
|
+
"zip",
|
|
230
|
+
"country",
|
|
231
|
+
"leadScore",
|
|
232
|
+
]).describe("Allowed contact field to update"),
|
|
233
|
+
value: z.any().describe("New value; strings/null for most fields, 0-100 number for leadScore"),
|
|
234
|
+
}),
|
|
235
|
+
])
|
|
236
|
+
.describe("The action to apply to the whole segment"),
|
|
237
|
+
limit: z.number().optional().describe("Max contacts to act on (default 100, max 1000)"),
|
|
238
|
+
}, async (params) => {
|
|
239
|
+
const result = await client.post("/api/v1/ai/tasks", { type: "segment_and_act", ...params, dryRun: true });
|
|
240
|
+
return formatResult(result);
|
|
241
|
+
});
|
|
242
|
+
server.tool("conduyt_ai_task_pipeline_cleanup", "AI task (PREVIEW ONLY): preview finding stale deals in a pipeline and tagging, moving, notifying, or reporting on them. Returns the stale deals and what WOULD happen, without persisting. Executing a mutating action is intentionally restricted to a human session in the app; agent/API-key callers can only preview.", {
|
|
243
|
+
pipelineId: z.string().describe("Pipeline UUID to scan"),
|
|
244
|
+
staleDays: z.number().optional().describe("Days without activity to consider a deal stale (default 30)"),
|
|
245
|
+
action: z.enum(["tag", "move_stage", "notify", "report_only"]).optional().describe("Action to preview against stale deals (default report_only)"),
|
|
246
|
+
targetStageId: z.string().optional().describe("Destination stage UUID (for move_stage)"),
|
|
247
|
+
tagId: z.string().optional().describe("Tag UUID to apply (for tag)"),
|
|
248
|
+
}, async (params) => {
|
|
249
|
+
const result = await client.post("/api/v1/ai/tasks", { type: "pipeline_cleanup", ...params, dryRun: true });
|
|
250
|
+
return formatResult(result);
|
|
251
|
+
});
|
|
38
252
|
}
|
package/dist/tools/ai.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { formatResult } from "../client.js";
|
|
3
3
|
export function registerAiTools(server, client) {
|
|
4
4
|
server.tool("conduyt_ai_insights", "Query AI-powered CRM insights. 8 query types available: pipeline_health (deals per stage, stale deals), sms_drip_status (SMS drip overview by provider/step), sms_responses_untouched (replied contacts with no follow-up), agent_followups (per-agent task performance), email_performance (open/click/reply rates), smart_view_summary (contact counts per saved view), team_activity (per-user activity counts), contact_import_history (recent import batches).", {
|
|
5
|
-
|
|
5
|
+
query: z.enum([
|
|
6
6
|
"pipeline_health",
|
|
7
7
|
"sms_drip_status",
|
|
8
8
|
"sms_responses_untouched",
|
|
@@ -12,9 +12,22 @@ export function registerAiTools(server, client) {
|
|
|
12
12
|
"team_activity",
|
|
13
13
|
"contact_import_history",
|
|
14
14
|
]).describe("Insight query type"),
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
filters: z
|
|
16
|
+
.object({
|
|
17
|
+
dateRange: z
|
|
18
|
+
.object({
|
|
19
|
+
from: z.string().describe("ISO 8601 date"),
|
|
20
|
+
to: z.string().describe("ISO 8601 date"),
|
|
21
|
+
})
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Date range to scope the query"),
|
|
24
|
+
provider: z.string().optional().describe("Filter by provider (e.g. for sms_drip_status)"),
|
|
25
|
+
pipelineId: z.string().optional().describe("Pipeline UUID (for pipeline_health)"),
|
|
26
|
+
userId: z.string().optional().describe("User UUID (for agent_followups)"),
|
|
27
|
+
smartListId: z.string().optional().describe("Smart list UUID (for smart_view_summary)"),
|
|
28
|
+
})
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Optional filters; supply the subset relevant to the query"),
|
|
18
31
|
}, async (params) => {
|
|
19
32
|
const result = await client.post("/api/v1/ai/insights", params);
|
|
20
33
|
return formatResult(result);
|
|
@@ -23,7 +23,9 @@ export function registerAutomationTools(server, client) {
|
|
|
23
23
|
name: z.string().describe("Automation name"),
|
|
24
24
|
description: z.string().optional().describe("Description of what this automation does"),
|
|
25
25
|
triggerEvent: z.string().describe("Trigger event (e.g. 'contact.created', 'deal.stage_changed', 'tag.added', 'scheduled')"),
|
|
26
|
-
kind: z.enum(["native", "n8n"]).optional().describe("Automation type — 'native' for built-in graph engine, 'n8n' for n8n webhook (
|
|
26
|
+
kind: z.enum(["native", "n8n"]).optional().describe("Automation type — 'native' for the built-in graph engine (use `actions`), 'n8n' for an external n8n webhook (requires n8nWorkflowId + n8nWebhookUrl). For agent-built automations use 'native'."),
|
|
27
|
+
n8nWorkflowId: z.string().optional().describe("n8n workflow ID (required when kind='n8n')"),
|
|
28
|
+
n8nWebhookUrl: z.string().optional().describe("n8n webhook URL (required when kind='n8n')"),
|
|
27
29
|
actions: z.any().optional().describe("Actions graph — v1: [{type, config}], v2: {nodes:[], startNodeId}"),
|
|
28
30
|
graphVersion: z.number().optional().describe("Graph version — 1 (legacy array) or 2 (node graph). Default 1"),
|
|
29
31
|
schedule: z.string().optional().describe("5-field cron expression (required if triggerEvent='scheduled')"),
|
package/dist/tools/bulk.js
CHANGED
|
@@ -68,4 +68,63 @@ export function registerBulkTools(server, client) {
|
|
|
68
68
|
const result = await client.get(`/api/v1/deals/export?${qp}`);
|
|
69
69
|
return formatResult(result);
|
|
70
70
|
});
|
|
71
|
+
server.tool("conduyt_bulk_tag_contacts", "Add one tag to many contacts in a single call. Requires contacts:edit.", {
|
|
72
|
+
contactIds: z.array(z.string()).describe("Contact UUIDs to tag"),
|
|
73
|
+
tagId: z.string().describe("Tag UUID to add to all of them"),
|
|
74
|
+
}, async (params) => {
|
|
75
|
+
const result = await client.post("/api/v1/bulk/contacts/tag", params);
|
|
76
|
+
return formatResult(result);
|
|
77
|
+
});
|
|
78
|
+
server.tool("conduyt_bulk_untag_contacts", "Remove one tag from many contacts in a single call. Requires contacts:edit.", {
|
|
79
|
+
contactIds: z.array(z.string()).describe("Contact UUIDs to untag"),
|
|
80
|
+
tagId: z.string().describe("Tag UUID to remove from all of them"),
|
|
81
|
+
}, async (params) => {
|
|
82
|
+
const result = await client.post("/api/v1/bulk/contacts/untag", params);
|
|
83
|
+
return formatResult(result);
|
|
84
|
+
});
|
|
85
|
+
server.tool("conduyt_bulk_edit_contacts", "Apply the SAME field changes to many contacts at once (one updates object → all ids). For different values per contact, use conduyt_bulk_update_contacts. Requires contacts:edit.", {
|
|
86
|
+
ids: z.array(z.string()).describe("Contact UUIDs to edit"),
|
|
87
|
+
updates: z.record(z.any()).describe("Fields to set on every listed contact, e.g. { assignedTo: '<uuid>', source: 'webinar', customFields: { tier: 'gold' } }. customFields merge into existing values."),
|
|
88
|
+
}, async (params) => {
|
|
89
|
+
const result = await client.post("/api/v1/bulk/contacts/edit", params);
|
|
90
|
+
return formatResult(result);
|
|
91
|
+
});
|
|
92
|
+
server.tool("conduyt_bulk_update_contacts", "Apply DIFFERENT field changes per contact in one call (each row carries its own id + fields). For the same change across many, use conduyt_bulk_edit_contacts. Requires contacts:edit.", {
|
|
93
|
+
updates: z
|
|
94
|
+
.array(z.record(z.any()))
|
|
95
|
+
.describe("Array of per-contact update objects; each MUST include 'id' plus the fields to change, e.g. [{ id: '<uuid>', firstName: 'Ada' }, …]"),
|
|
96
|
+
}, async (params) => {
|
|
97
|
+
const result = await client.post("/api/v1/bulk/contacts/update", params);
|
|
98
|
+
return formatResult(result);
|
|
99
|
+
});
|
|
100
|
+
server.tool("conduyt_bulk_delete_contacts", "Soft-delete many contacts in a single call. Requires contacts:delete.", {
|
|
101
|
+
ids: z.array(z.string()).describe("Contact UUIDs to delete"),
|
|
102
|
+
}, async (params) => {
|
|
103
|
+
const result = await client.post("/api/v1/bulk/contacts/delete", params);
|
|
104
|
+
return formatResult(result);
|
|
105
|
+
});
|
|
106
|
+
server.tool("conduyt_bulk_dnc_contacts", "Add or remove a Do-Not-Contact / litigator mark for many contacts (compliance action). The mark is keyed on each contact's phone number, so contacts without a phone are skipped. Base permission is contacts:edit; removing any mark, or adding a litigator mark, additionally requires an owner/admin role server-side (others get 403).", {
|
|
107
|
+
contactIds: z.array(z.string()).describe("Contact UUIDs (those without a phone number are skipped)"),
|
|
108
|
+
action: z.enum(["add", "remove"]).describe("Add or remove the mark"),
|
|
109
|
+
tier: z.enum(["dnc", "litigator"]).describe("Which list: 'dnc' (standard do-not-contact) or 'litigator' (known litigator; admin-only to add)"),
|
|
110
|
+
reason: z.string().optional().describe("Reason recorded with the action for the compliance audit trail"),
|
|
111
|
+
}, async (params) => {
|
|
112
|
+
const result = await client.post("/api/v1/bulk/contacts/dnc", params);
|
|
113
|
+
return formatResult(result);
|
|
114
|
+
});
|
|
115
|
+
server.tool("conduyt_bulk_edit_deals", "Apply the SAME field changes to many deals at once (one updates object → all ids) — e.g. reassign a set of deals to a user. For different values per deal, use conduyt_bulk_update_deals. Requires deals:edit.", {
|
|
116
|
+
ids: z.array(z.string()).describe("Deal UUIDs to edit"),
|
|
117
|
+
updates: z.record(z.any()).describe("Fields to set on every listed deal, e.g. { assignedTo: '<uuid>', stageId: '<uuid>', priority: 'high' }"),
|
|
118
|
+
}, async (params) => {
|
|
119
|
+
const result = await client.post("/api/v1/bulk/deals/edit", params);
|
|
120
|
+
return formatResult(result);
|
|
121
|
+
});
|
|
122
|
+
server.tool("conduyt_bulk_update_deals", "Apply DIFFERENT field changes per deal in one call (each row carries its own id + fields). For the same change across many, use conduyt_bulk_edit_deals. Requires deals:edit.", {
|
|
123
|
+
updates: z
|
|
124
|
+
.array(z.record(z.any()))
|
|
125
|
+
.describe("Array of per-deal update objects; each MUST include 'id' plus the fields to change, e.g. [{ id: '<uuid>', value: 5000 }, …]"),
|
|
126
|
+
}, async (params) => {
|
|
127
|
+
const result = await client.post("/api/v1/bulk/deals/update", params);
|
|
128
|
+
return formatResult(result);
|
|
129
|
+
});
|
|
71
130
|
}
|
package/dist/tools/contacts.js
CHANGED
|
@@ -17,7 +17,7 @@ export function registerContactTools(server, client) {
|
|
|
17
17
|
if (source)
|
|
18
18
|
params.set("source", source);
|
|
19
19
|
if (assignedTo)
|
|
20
|
-
params.set("
|
|
20
|
+
params.set("assigned_to", assignedTo);
|
|
21
21
|
if (page)
|
|
22
22
|
params.set("page", String(page));
|
|
23
23
|
if (per_page)
|
|
@@ -38,7 +38,7 @@ export function registerContactTools(server, client) {
|
|
|
38
38
|
phone: z.string().optional().describe("Phone number"),
|
|
39
39
|
company: z.string().optional().describe("Company name"),
|
|
40
40
|
source: z.string().optional().describe("Lead source (e.g. 'website', 'referral', 'import')"),
|
|
41
|
-
tags: z.array(z.string()).optional().describe("Tag
|
|
41
|
+
tags: z.array(z.string()).optional().describe("Tag UUIDs to apply (tags must already exist — create them with conduyt_create_tag first)"),
|
|
42
42
|
}, async (params) => {
|
|
43
43
|
const result = await client.post("/api/v1/contacts", params);
|
|
44
44
|
return formatResult(result);
|
|
@@ -54,11 +54,11 @@ export function registerContactTools(server, client) {
|
|
|
54
54
|
const result = await client.patch(`/api/v1/contacts/${id}`, updates);
|
|
55
55
|
return formatResult(result);
|
|
56
56
|
});
|
|
57
|
-
server.tool("conduyt_add_tags", "Add one or more tags to a contact.
|
|
57
|
+
server.tool("conduyt_add_tags", "Add one or more existing tags to a contact by tag UUID. Tags must already exist (create them with conduyt_create_tag); this does NOT create tags.", {
|
|
58
58
|
contactId: z.string().describe("Contact UUID"),
|
|
59
|
-
|
|
60
|
-
}, async ({ contactId,
|
|
61
|
-
const result = await client.post(`/api/v1/contacts/${contactId}/tags`, {
|
|
59
|
+
tagIds: z.array(z.string()).describe("Tag UUIDs to add (must already exist)"),
|
|
60
|
+
}, async ({ contactId, tagIds }) => {
|
|
61
|
+
const result = await client.post(`/api/v1/contacts/${contactId}/tags`, { tagIds });
|
|
62
62
|
return formatResult(result);
|
|
63
63
|
});
|
|
64
64
|
}
|
package/dist/tools/deals.js
CHANGED
|
@@ -12,11 +12,11 @@ export function registerDealTools(server, client) {
|
|
|
12
12
|
}, async ({ pipelineId, stageId, assignedTo, status, search, page, per_page }) => {
|
|
13
13
|
const params = new URLSearchParams();
|
|
14
14
|
if (pipelineId)
|
|
15
|
-
params.set("
|
|
15
|
+
params.set("pipeline", pipelineId);
|
|
16
16
|
if (stageId)
|
|
17
|
-
params.set("
|
|
17
|
+
params.set("stage", stageId);
|
|
18
18
|
if (assignedTo)
|
|
19
|
-
params.set("
|
|
19
|
+
params.set("assigned_to", assignedTo);
|
|
20
20
|
if (status)
|
|
21
21
|
params.set("status", status);
|
|
22
22
|
if (search)
|
|
@@ -70,4 +70,38 @@ export function registerDealTools(server, client) {
|
|
|
70
70
|
const result = await client.patch(`/api/v1/deals/${id}`, updates);
|
|
71
71
|
return formatResult(result);
|
|
72
72
|
});
|
|
73
|
+
server.tool("conduyt_import_deals", "Bulk-import deals from a list of rows in one call. Runs on the canonical import engine — pipelines and stages are resolved by NAME (create them first with conduyt_create_pipeline), contacts by email. Deduplicates on title + pipeline + contact, and intentionally does NOT fire deal-created automations/webhooks/SMS/email (bulk imports are side-effect-free by design). Requires an owner/admin API key. Returns the created/skipped/duplicate counts and any per-row errors.", {
|
|
74
|
+
deals: z
|
|
75
|
+
.array(z.object({
|
|
76
|
+
title: z.string().describe("Deal title (required)"),
|
|
77
|
+
pipelineName: z.string().describe("Pipeline name (resolved by name; required)"),
|
|
78
|
+
stageName: z.string().optional().describe("Stage name within the pipeline; defaults to the first stage if omitted or unmatched"),
|
|
79
|
+
contactEmail: z.string().optional().describe("Email of an existing contact to link (resolved by email)"),
|
|
80
|
+
companyId: z.string().optional().describe("Company UUID to link (must belong to your account; invalid/deleted/cross-account IDs are reported as row errors)"),
|
|
81
|
+
value: z.number().optional().describe("Deal value (numeric, in the deal's currency)"),
|
|
82
|
+
currency: z.string().optional().describe("ISO 4217 currency code, e.g. 'GBP'. Defaults to USD."),
|
|
83
|
+
status: z.string().optional().describe("Deal status (default 'open')"),
|
|
84
|
+
priority: z.string().optional().describe("Deal priority, e.g. 'low', 'medium', 'high'"),
|
|
85
|
+
probability: z.number().min(0).max(1).optional().describe("Win probability as a 0–1 fraction (e.g. 0.75 = 75%); clamped to 0–1"),
|
|
86
|
+
expectedCloseDate: z.string().optional().describe("Expected close date (ISO 8601, e.g. '2026-09-30')"),
|
|
87
|
+
customFields: z.record(z.any()).optional().describe("Structured custom-field values keyed by field key, e.g. { limit_of_indemnity: 5000000 }"),
|
|
88
|
+
}))
|
|
89
|
+
.describe("Deals to import (1 or more rows; max matches the account import cap)"),
|
|
90
|
+
}, async ({ deals }) => {
|
|
91
|
+
const created = (await client.post("/api/v1/imports", {
|
|
92
|
+
objectType: "opportunities",
|
|
93
|
+
format: "json",
|
|
94
|
+
fileName: "deal-import.csv",
|
|
95
|
+
rows: deals,
|
|
96
|
+
}));
|
|
97
|
+
const jobId = created.data?.id;
|
|
98
|
+
if (!jobId) {
|
|
99
|
+
return formatResult({
|
|
100
|
+
error: "Import job was created but no job id was returned",
|
|
101
|
+
createResponse: created,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const result = await client.post(`/api/v1/imports/${jobId}/process`, {});
|
|
105
|
+
return formatResult({ jobId, ...result });
|
|
106
|
+
});
|
|
73
107
|
}
|
package/dist/tools/discovery.js
CHANGED
|
@@ -11,17 +11,14 @@ export function registerDiscoveryTools(server, client) {
|
|
|
11
11
|
});
|
|
12
12
|
server.tool("conduyt_search", "Global search across all CRM entities — contacts, deals, companies, tasks.", {
|
|
13
13
|
query: z.string().describe("Search query — matches names, emails, phone numbers, deal titles"),
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}, async ({ query, type, page, per_page }) => {
|
|
14
|
+
limit: z.number().optional().describe("Max results to return (default 50)"),
|
|
15
|
+
cursor: z.string().optional().describe("Pagination cursor from a previous response's nextCursor"),
|
|
16
|
+
}, async ({ query, limit, cursor }) => {
|
|
18
17
|
const params = new URLSearchParams({ q: query });
|
|
19
|
-
if (
|
|
20
|
-
params.set("
|
|
21
|
-
if (
|
|
22
|
-
params.set("
|
|
23
|
-
if (per_page)
|
|
24
|
-
params.set("per_page", String(per_page));
|
|
18
|
+
if (limit)
|
|
19
|
+
params.set("limit", String(limit));
|
|
20
|
+
if (cursor)
|
|
21
|
+
params.set("cursor", cursor);
|
|
25
22
|
const result = await client.get(`/api/v1/search?${params}`);
|
|
26
23
|
return formatResult(result);
|
|
27
24
|
});
|
package/dist/tools/invoices.js
CHANGED
|
@@ -47,7 +47,9 @@ export function registerInvoiceTools(server, client) {
|
|
|
47
47
|
notes: z.string().optional().describe("Notes visible to client"),
|
|
48
48
|
terms: z.string().optional().describe("Payment terms"),
|
|
49
49
|
}, async (params) => {
|
|
50
|
-
|
|
50
|
+
// The create route requires a valid currency (no server-side default),
|
|
51
|
+
// so always send one — honoring the documented USD default.
|
|
52
|
+
const result = await client.post("/api/v1/invoices", { ...params, currency: params.currency ?? "USD" });
|
|
51
53
|
return formatResult(result);
|
|
52
54
|
});
|
|
53
55
|
server.tool("conduyt_update_invoice", "Update a draft invoice — change items, contact, due date, or terms. Cannot update paid or voided invoices.", {
|
|
@@ -19,7 +19,7 @@ export function registerNotificationTools(server, client) {
|
|
|
19
19
|
message: z.string().describe("Notification body (max 1000 chars)"),
|
|
20
20
|
userId: z.string().optional().describe("Target user UUID (defaults to API key owner)"),
|
|
21
21
|
url: z.string().optional().describe("Deep link URL when notification is clicked"),
|
|
22
|
-
metadata: z.any().optional().describe("Additional data attached to the notification"),
|
|
22
|
+
metadata: z.record(z.string(), z.any()).optional().describe("Additional data attached to the notification (must be a key/value object)"),
|
|
23
23
|
push: z.boolean().optional().describe("Send web push notification (default true)"),
|
|
24
24
|
}, async (params) => {
|
|
25
25
|
const result = await client.post("/api/v1/notifications", params);
|
package/dist/tools/tasks.js
CHANGED
|
@@ -3,25 +3,49 @@ import { formatResult } from "../client.js";
|
|
|
3
3
|
export function registerTaskTools(server, client) {
|
|
4
4
|
server.tool("conduyt_list_tasks", "List tasks with filtering by assignee, status, or due date. Returns task title, description, due date, priority, and assignee.", {
|
|
5
5
|
assignedTo: z.string().optional().describe("Filter by assigned user UUID"),
|
|
6
|
-
status: z.enum(["
|
|
7
|
-
|
|
6
|
+
status: z.enum(["todo", "in_progress", "done"]).optional().describe("Filter by task status"),
|
|
7
|
+
priority: z.enum(["low", "medium", "high", "urgent"]).optional().describe("Filter by task priority"),
|
|
8
|
+
dueFrom: z.string().optional().describe("Filter tasks due on/after this date (ISO 8601, e.g. '2026-04-30')"),
|
|
9
|
+
dueTo: z.string().optional().describe("Filter tasks due on/before this date (ISO 8601)"),
|
|
10
|
+
overdue: z.boolean().optional().describe("Filter to overdue open tasks"),
|
|
11
|
+
tab: z.enum(["my_tasks", "all", "completed"]).optional().describe("Task queue tab filter"),
|
|
12
|
+
search: z.string().optional().describe("Search task title or description"),
|
|
8
13
|
contactId: z.string().optional().describe("Filter tasks linked to a specific contact"),
|
|
14
|
+
dealId: z.string().optional().describe("Filter tasks linked to a specific deal"),
|
|
9
15
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
10
16
|
per_page: z.number().optional().describe("Results per page (default 25, max 100)"),
|
|
11
|
-
|
|
17
|
+
sort: z.enum(["createdAt", "updatedAt", "dueDate", "priority", "title", "status"]).optional().describe("Sort field"),
|
|
18
|
+
order: z.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
19
|
+
}, async ({ assignedTo, status, priority, dueFrom, dueTo, overdue, tab, search, contactId, dealId, page, per_page, sort, order }) => {
|
|
12
20
|
const params = new URLSearchParams();
|
|
13
21
|
if (assignedTo)
|
|
14
22
|
params.set("assignedTo", assignedTo);
|
|
15
23
|
if (status)
|
|
16
24
|
params.set("status", status);
|
|
17
|
-
if (
|
|
18
|
-
params.set("
|
|
25
|
+
if (priority)
|
|
26
|
+
params.set("priority", priority);
|
|
27
|
+
if (dueFrom)
|
|
28
|
+
params.set("dueFrom", dueFrom);
|
|
29
|
+
if (dueTo)
|
|
30
|
+
params.set("dueTo", dueTo);
|
|
31
|
+
if (overdue !== undefined)
|
|
32
|
+
params.set("overdue", String(overdue));
|
|
33
|
+
if (tab)
|
|
34
|
+
params.set("tab", tab);
|
|
35
|
+
if (search)
|
|
36
|
+
params.set("search", search);
|
|
19
37
|
if (contactId)
|
|
20
38
|
params.set("contactId", contactId);
|
|
39
|
+
if (dealId)
|
|
40
|
+
params.set("dealId", dealId);
|
|
21
41
|
if (page)
|
|
22
42
|
params.set("page", String(page));
|
|
23
43
|
if (per_page)
|
|
24
44
|
params.set("per_page", String(per_page));
|
|
45
|
+
if (sort)
|
|
46
|
+
params.set("sort", sort);
|
|
47
|
+
if (order)
|
|
48
|
+
params.set("order", order);
|
|
25
49
|
const result = await client.get(`/api/v1/tasks?${params}`);
|
|
26
50
|
return formatResult(result);
|
|
27
51
|
});
|
|
@@ -30,6 +54,7 @@ export function registerTaskTools(server, client) {
|
|
|
30
54
|
description: z.string().optional().describe("Task description"),
|
|
31
55
|
dueDate: z.string().optional().describe("Due date (ISO 8601)"),
|
|
32
56
|
priority: z.enum(["low", "medium", "high", "urgent"]).optional().describe("Task priority"),
|
|
57
|
+
status: z.enum(["todo", "in_progress", "done"]).optional().describe("Task status (default: todo)"),
|
|
33
58
|
assignedTo: z.string().optional().describe("Assign to user UUID"),
|
|
34
59
|
contactId: z.string().optional().describe("Link to a contact UUID"),
|
|
35
60
|
dealId: z.string().optional().describe("Link to a deal UUID"),
|
package/dist/tools/users.js
CHANGED
|
@@ -33,7 +33,7 @@ export function registerUserTools(server, client) {
|
|
|
33
33
|
const result = await client.post("/api/v1/users", params);
|
|
34
34
|
return formatResult(result);
|
|
35
35
|
});
|
|
36
|
-
server.tool("conduyt_update_user", "Update a team member's profile, role, or permissions. Cannot modify your own role.", {
|
|
36
|
+
server.tool("conduyt_update_user", "Update a team member's profile, role, or permissions. Cannot modify your own role. To activate/deactivate a user, use conduyt_deactivate_user / conduyt_reactivate_user (status cannot be changed here).", {
|
|
37
37
|
id: z.string().describe("User UUID"),
|
|
38
38
|
firstName: z.string().optional().describe("Updated first name"),
|
|
39
39
|
lastName: z.string().optional().describe("Updated last name"),
|
|
@@ -41,7 +41,6 @@ export function registerUserTools(server, client) {
|
|
|
41
41
|
phone: z.string().optional().describe("Updated phone"),
|
|
42
42
|
role: z.enum(["member", "admin", "owner"]).optional().describe("Updated role"),
|
|
43
43
|
permissions: z.array(z.string()).optional().describe("Updated permission scopes"),
|
|
44
|
-
isActive: z.boolean().optional().describe("Activate or deactivate the user"),
|
|
45
44
|
}, async ({ id, ...updates }) => {
|
|
46
45
|
const result = await client.patch(`/api/v1/users/${id}`, updates);
|
|
47
46
|
return formatResult(result);
|
|
@@ -62,4 +61,18 @@ export function registerUserTools(server, client) {
|
|
|
62
61
|
const result = await client.post("/api/v1/users/invite", params);
|
|
63
62
|
return formatResult(result);
|
|
64
63
|
});
|
|
64
|
+
server.tool("conduyt_deactivate_user", "Deactivate (soft-disable) a team member so they can no longer sign in. Optionally reassign their open records to another user. Requires the users:deactivate permission.", {
|
|
65
|
+
id: z.string().describe("User UUID to deactivate"),
|
|
66
|
+
reassignTo: z.string().optional().describe("User UUID to reassign the deactivated user's records to"),
|
|
67
|
+
}, async ({ id, reassignTo }) => {
|
|
68
|
+
const qs = reassignTo ? `?reassign_to=${encodeURIComponent(reassignTo)}` : "";
|
|
69
|
+
const result = await client.post(`/api/v1/users/${id}/deactivate${qs}`, {});
|
|
70
|
+
return formatResult(result);
|
|
71
|
+
});
|
|
72
|
+
server.tool("conduyt_reactivate_user", "Reactivate a previously deactivated team member, restoring their access.", {
|
|
73
|
+
id: z.string().describe("User UUID to reactivate"),
|
|
74
|
+
}, async ({ id }) => {
|
|
75
|
+
const result = await client.post(`/api/v1/users/${id}/reactivate`, {});
|
|
76
|
+
return formatResult(result);
|
|
77
|
+
});
|
|
65
78
|
}
|