conduyt-mcp 4.98.0 → 4.100.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 +2 -0
- package/dist/register-all.js +4 -0
- package/dist/tool-annotations.js +2 -0
- package/dist/tools/dialer-settings.js +1 -1
- package/dist/tools/my-profile.d.ts +8 -0
- package/dist/tools/my-profile.js +10 -0
- package/dist/tools/short-links.d.ts +9 -0
- package/dist/tools/short-links.js +14 -0
- package/package.json +1 -1
- package/scripts/coverage-manifest.json +5 -5
- package/scripts/coverage-matrix.json +15 -11
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
36
36
|
| `conduyt_get_stage_rules` / `conduyt_set_stage_rules` | Read / set one stage's rule (`requiredFields` incl. `custom:<fieldKey>`, `requiredDocuments` 0-20, lost-reason label and options, `lostReasonAllowOther`, `entryByAutomationOnly`: only an automation can move or create a deal into the stage, every other entry is refused 422 `stage_automation_only`; `conduyt_list_pipelines` shows it on each stage; `closesOtherOpenDeals` (+ `closesOtherOpenDealsReason`, 1-200 characters): a deal entering the stage marks the contact's other open deals lost; `assignsContactOwner`: the entering deal's owner becomes the contact owner, the previous contact owner stays a collaborator); other stages untouched; revision-aware, retried once on a 409 |
|
|
37
37
|
| `conduyt_get_calling_hours` / `conduyt_update_calling_hours` | Read the window the dialer enforces (`effectiveCallingWindow`: `source`, `enabled`, `start`/`end` HH:MM, `timezone` or null = contact zone, then number area code, then account) / set `settings.callingWindow` (`enabled`, `start`, `end` always sent, `end` after `start`, `timezone` trimmed and recognised by the API (omit keeps the stored zone, null clears it); `enabled: false` needs `acknowledgeAnyHour: true`, else 400 `calling_hours_acknowledgement_required`) |
|
|
38
38
|
| `conduyt_list_email_opt_outs` / `conduyt_add_email_opt_out` / `conduyt_resubscribe_email_opt_out` | The email opt-out list (owner / admin key; hash-only rows `{ id, source, reason, createdAt }`): list (`page`, `perPage` 1-100), add an address by hand (`reason` 1-200 required; 201 new row, 200 when an unsubscribe-link or Mailchimp row is promoted to manual, 409 with the existing row when already listed by hand) and take an address off (`{ removed }`) |
|
|
39
|
+
| `conduyt_get_my_profile` | Read the caller's own identity (`GET /users/me`, same as `/auth/me`): `data.user` incl. `ringtone` (the incoming-ring slug, null = Conduyt Default), `data.account`, role, permissions, `apiKey`, `accounts`. Editing the profile is a signed-in person's action and is not exposed to API keys |
|
|
40
|
+
| `conduyt_create_short_link` | Make (or reuse) the workspace's Conduyt short link for a public link-shortener address the Message check flagged (`POST /short-links`, body `{ url }`; key needs the `messages` scope, member needs communications.create). Not shortenable: our own host, merge fields, `mailto:`/`tel:`/`sms:`, or any address the Message check does not flag as a link shortener (400 `not_shortenable`). Other errors verbatim, never retried: 400 `invalid_body`, 403 `forbidden`, 413 `body_too_large`, 429 `rate_limited` (with Retry-After), 503 `short_link_unavailable` (nothing changed) |
|
|
39
41
|
| `conduyt_get_deal_card_fields` / `conduyt_set_deal_card_fields` | Read / replace the kanban card fields (1-8, built-ins or `custom:<fieldKey>`) |
|
|
40
42
|
| `conduyt_get_settings_revision` | The account `settingsRevision` (plus stored stage rules and rot days): pass it as `expectedSettingsRevision` to `conduyt_archive_pipeline` / `conduyt_delete_stage` so a stale one is refused 409 `stale_settings_revision` |
|
|
41
43
|
| `conduyt_get_pipeline_rot_days` / `conduyt_set_pipeline_rot_days` | Read / set the stale-in-stage threshold per pipeline (`pipelineRotDays`, 1-90 days); only the pipelines sent change; revision-aware, retried once on a 409 |
|
package/dist/register-all.js
CHANGED
|
@@ -32,6 +32,8 @@ import { registerBulkTools } from "./tools/bulk.js";
|
|
|
32
32
|
import { registerBatchOperationTools } from "./tools/batch-operations.js";
|
|
33
33
|
import { registerDncTools } from "./tools/dnc.js";
|
|
34
34
|
import { registerEmailOptOutTools } from "./tools/email-opt-out.js";
|
|
35
|
+
import { registerShortLinkTools } from "./tools/short-links.js";
|
|
36
|
+
import { registerMyProfileTools } from "./tools/my-profile.js";
|
|
35
37
|
import { registerSavedFilterTools } from "./tools/saved-filters.js";
|
|
36
38
|
import { registerDripCampaignTools } from "./tools/drip-campaigns.js";
|
|
37
39
|
import { registerScoringTools } from "./tools/scoring.js";
|
|
@@ -91,6 +93,8 @@ export function registerAllTools(server, client) {
|
|
|
91
93
|
registerBatchOperationTools(server, client);
|
|
92
94
|
registerDncTools(server, client);
|
|
93
95
|
registerEmailOptOutTools(server, client);
|
|
96
|
+
registerShortLinkTools(server, client);
|
|
97
|
+
registerMyProfileTools(server, client);
|
|
94
98
|
registerSavedFilterTools(server, client);
|
|
95
99
|
registerDripCampaignTools(server, client);
|
|
96
100
|
registerScoringTools(server, client);
|
package/dist/tool-annotations.js
CHANGED
|
@@ -221,6 +221,8 @@ export const TOOL_ANNOTATIONS = {
|
|
|
221
221
|
conduyt_list_email_opt_outs: ro("List Email Opt-Outs"),
|
|
222
222
|
conduyt_add_email_opt_out: update("Add Email Opt-Out"), // a repeat is refused 409 (already listed by hand) before any write, so it lands on the same state; adding a block is protective
|
|
223
223
|
conduyt_resubscribe_email_opt_out: destroy("Resubscribe Email Address"), // consent change: removing the row re-enables every customer-facing email to the address
|
|
224
|
+
conduyt_get_my_profile: ro("Get My Profile"),
|
|
225
|
+
conduyt_create_short_link: update("Create Short Link"), // one link per account and target: a repeat returns the same link (idempotent), nothing existing is changed
|
|
224
226
|
conduyt_list_saved_filters: ro("List Saved Filters"),
|
|
225
227
|
conduyt_create_saved_filter: create("Create Saved Filter"),
|
|
226
228
|
conduyt_update_saved_filter: update("Update Saved Filter"),
|
|
@@ -123,7 +123,7 @@ export function registerCallingHoursTools(server, client) {
|
|
|
123
123
|
const result = (await client.get("/api/v1/settings"));
|
|
124
124
|
return formatResult({ data: { effectiveCallingWindow: result?.data?.effectiveCallingWindow ?? null } });
|
|
125
125
|
});
|
|
126
|
-
strictInput(server.tool("conduyt_update_calling_hours", "Set the dialer's calling hours (settings.callingWindow via PATCH /api/v1/settings; admin key, settings:edit). enabled, start and end are always sent together (the API
|
|
126
|
+
strictInput(server.tool("conduyt_update_calling_hours", "Set the dialer's calling hours (settings.callingWindow via PATCH /api/v1/settings; admin key, settings:edit). enabled, start and end are always sent together (the API MERGES them into the saved window, so a key you leave out keeps its stored value): start / end are HH:MM 24 hour and end must be after start; timezone is an IANA zone such as America/Chicago the hours are judged in; it is sent trimmed and otherwise as given, and the API recognises it (it also accepts compatibility spellings such as AKST), so an unknown zone comes back as the settings route's 422 (validation), printed as sent. OMITTING timezone keeps the stored zone (the API merges the window); timezone: null clears it, and then the hours follow the contact's zone, then the called number's area code, then the account's. enabled: false switches the calling-hours check OFF (calls at any hour) and then needs acknowledgeAnyHour: true, meaning your team owns the calling-time rules; the acknowledgement is not stored and the change is written to the audit log. Without it the API refuses 400 calling_hours_acknowledgement_required. The first save over an older saved shape (callingHours, dialer.*, voice.*) moves it into callingWindow. Read the current value with conduyt_get_calling_hours first. A stale settings revision is handed back as a conflict with the current settings.", {
|
|
127
127
|
enabled: z.boolean().describe("false switches the calling-hours check off; it then needs acknowledgeAnyHour: true"),
|
|
128
128
|
start: z.string().regex(CALLING_TIME, "start must be HH:MM, 24 hour").describe("HH:MM, 24 hour"),
|
|
129
129
|
end: z.string().regex(CALLING_TIME, "end must be HH:MM, 24 hour").describe("HH:MM, 24 hour; must be after start"),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The caller's own profile read (app route GET /api/v1/users/me, the same handler as GET /auth/me). data.user carries the
|
|
5
|
+
* incoming-ring slug (#385) as `ringtone`. PATCH /users/me is deliberately not a tool: it is a signed-in person's action
|
|
6
|
+
* (profile, ringtone, password) and the API refuses an API key, so a tool for it would only ever return 403.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerMyProfileTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { formatResult } from "../client.js";
|
|
2
|
+
import { strictInput } from "./strict.js";
|
|
3
|
+
/**
|
|
4
|
+
* The caller's own profile read (app route GET /api/v1/users/me, the same handler as GET /auth/me). data.user carries the
|
|
5
|
+
* incoming-ring slug (#385) as `ringtone`. PATCH /users/me is deliberately not a tool: it is a signed-in person's action
|
|
6
|
+
* (profile, ringtone, password) and the API refuses an API key, so a tool for it would only ever return 403.
|
|
7
|
+
*/
|
|
8
|
+
export function registerMyProfileTools(server, client) {
|
|
9
|
+
strictInput(server.tool("conduyt_get_my_profile", "Read the caller's own identity (GET /api/v1/users/me, identical to GET /auth/me): data.user (id, email, firstName, lastName, avatarUrl, phone, birthdayMonth, birthdayDay, tourSeenAt, and ringtone: the slug of the incoming ring this person hears, or null = Conduyt Default; the slug is one of classic, soft-bell, marimba, digital-pulse, rising-chime, office-trill, double-ring, pocket-melody, retro-cell, kalimba, uplift), data.account (the current account, name at data.account.name), role, permissions, leadVisibilityMode, apiKey (the key's own metadata when called with an API key, else null) and accounts. Any authenticated caller, including a scoped key, may read it. Editing the profile (name, phone, birthday, ringtone, password) is a signed-in person's action in the app and is not available to an API key.", {}, async () => formatResult(await client.get("/api/v1/users/me"))));
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The composer's short link (#466; app route POST /api/v1/short-links, "Replace with a Conduyt short link"). The route's own
|
|
5
|
+
* checks (what is shortenable, the body size, the per-minute budget) stay with the API; this tool sends { url } as given and
|
|
6
|
+
* surfaces every refusal verbatim. A POST is sent exactly once by the client, so a 503 (nothing changed) or a 429 (with its
|
|
7
|
+
* Retry-After) reaches the agent as the first answer and is never replayed here.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerShortLinkTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatResult } from "../client.js";
|
|
3
|
+
import { strictInput } from "./strict.js";
|
|
4
|
+
/**
|
|
5
|
+
* The composer's short link (#466; app route POST /api/v1/short-links, "Replace with a Conduyt short link"). The route's own
|
|
6
|
+
* checks (what is shortenable, the body size, the per-minute budget) stay with the API; this tool sends { url } as given and
|
|
7
|
+
* surfaces every refusal verbatim. A POST is sent exactly once by the client, so a 503 (nothing changed) or a 429 (with its
|
|
8
|
+
* Retry-After) reaches the agent as the first answer and is never replayed here.
|
|
9
|
+
*/
|
|
10
|
+
export function registerShortLinkTools(server, client) {
|
|
11
|
+
strictInput(server.tool("conduyt_create_short_link", "Make (or reuse) this workspace's Conduyt short link for a public link-shortener address that the Message check flagged (POST /api/v1/short-links; an API key needs the 'messages' scope, an sms key is accepted, and the member needs communications.create): put the returned short link in the message in place of the flagged address. The short link redirects to the address exactly as given; there is one link per workspace and address, so a repeat returns the same link. Shortenable: a web link the link check would fetch and the Message check flagged as a public link shortener. NOT shortenable (400 not_shortenable, before any work): an address on our own host (unsubscribe, one-time booking and file links, click tracking), an address with a merge field, mailto:, tel: or sms: addresses, one of our own short links, or any address the Message check does not flag as a link shortener. Answers 200 { data: { url: the short link, target: the address given } }. Errors, surfaced verbatim and never retried: 400 invalid_body (url missing, not a string or an unknown key) / not_shortenable; 403 forbidden; 413 body_too_large; 429 rate_limited (30 per minute per workspace and member; the Retry-After seconds are in the error); 503 short_link_unavailable (the link could not be made; nothing changed, try again later).", {
|
|
12
|
+
url: z.string().min(1).describe("The flagged public link-shortener address to replace, e.g. https://bit.ly/abc123 (sent exactly as given; the redirect goes to this address)"),
|
|
13
|
+
}, async ({ url }) => formatResult(await client.post("/api/v1/short-links", { url }))));
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
"planned": "callable in principle; a typed tool is wanted but not built yet"
|
|
6
6
|
},
|
|
7
7
|
"rules": [
|
|
8
|
-
{
|
|
9
|
-
"match": "POST /api/v1/short-links",
|
|
10
|
-
"status": "planned",
|
|
11
|
-
"reason": "composer short-links route; the tri-surface rule owes it a real conduyt_* tool, board #466 (MCP/CLI parity for the composer short-links route)"
|
|
12
|
-
},
|
|
13
8
|
{
|
|
14
9
|
"match": "GET /api/v1/call-flows/functions/:id",
|
|
15
10
|
"status": "planned",
|
|
@@ -205,6 +200,11 @@
|
|
|
205
200
|
"status": "planned",
|
|
206
201
|
"reason": "company subroutes beyond core CRUD"
|
|
207
202
|
},
|
|
203
|
+
{
|
|
204
|
+
"match": "PATCH /api/v1/users/me",
|
|
205
|
+
"status": "excluded",
|
|
206
|
+
"reason": "a signed-in person's own profile edit (name, phone, birthday, ringtone, password): session only (assertSessionOnly), never an API key's; GET /users/me is covered by conduyt_get_my_profile"
|
|
207
|
+
},
|
|
208
208
|
{
|
|
209
209
|
"match": "/api/v1/users",
|
|
210
210
|
"status": "planned",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-10-
|
|
3
|
-
"toolCount":
|
|
2
|
+
"generatedAt": "2026-10-25T09:09:54.777Z",
|
|
3
|
+
"toolCount": 366,
|
|
4
4
|
"totalEndpoints": 1107,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
6
|
+
"covered": 322,
|
|
7
|
+
"excluded": 331,
|
|
8
|
+
"planned": 454,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -4423,8 +4423,10 @@
|
|
|
4423
4423
|
]
|
|
4424
4424
|
},
|
|
4425
4425
|
"POST /api/v1/short-links": {
|
|
4426
|
-
"status": "
|
|
4427
|
-
"
|
|
4426
|
+
"status": "covered",
|
|
4427
|
+
"tools": [
|
|
4428
|
+
"conduyt_create_short_link"
|
|
4429
|
+
]
|
|
4428
4430
|
},
|
|
4429
4431
|
"GET /api/v1/smart-lists": {
|
|
4430
4432
|
"status": "covered",
|
|
@@ -4852,12 +4854,14 @@
|
|
|
4852
4854
|
]
|
|
4853
4855
|
},
|
|
4854
4856
|
"GET /api/v1/users/me": {
|
|
4855
|
-
"status": "
|
|
4856
|
-
"
|
|
4857
|
+
"status": "covered",
|
|
4858
|
+
"tools": [
|
|
4859
|
+
"conduyt_get_my_profile"
|
|
4860
|
+
]
|
|
4857
4861
|
},
|
|
4858
4862
|
"PATCH /api/v1/users/me": {
|
|
4859
|
-
"status": "
|
|
4860
|
-
"reason": "
|
|
4863
|
+
"status": "excluded",
|
|
4864
|
+
"reason": "a signed-in person's own profile edit (name, phone, birthday, ringtone, password): session only (assertSessionOnly), never an API key's; GET /users/me is covered by conduyt_get_my_profile"
|
|
4861
4865
|
},
|
|
4862
4866
|
"GET /api/v1/users/me/tour": {
|
|
4863
4867
|
"status": "planned",
|