conduyt-mcp 4.85.0 → 4.86.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.
@@ -2,6 +2,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { ConduytClient } from "../client.js";
3
3
  /** (card ownership per lane, app 10-08/09) the action options an agent can set; the catalog does not carry action config, so this is the one place they are documented */
4
4
  export declare const CARD_OWNERSHIP_ACTIONS_NOTE = " Card ownership per lane: assign_to_user config {userId | userIds (round robin) | groupId, target|applyTo: contact|deal|both, overwriteExisting, laneSource, lanePipelineId, laneStageId}: laneSource names a card lane (the Deal.source its Auto-Create step wrote, e.g. 'GSM', max 500 chars) and makes the deal leg assign the contact's newest OPEN card from that lane (inside lanePipelineId when set) instead of the run's deal; when the contact has none and lanePipelineId is set, the card is created there (laneStageId, else the pipeline's first stage) already owned by the user; lanePipelineId or laneStageId without laneSource, or laneStageId without lanePipelineId, is refused; a lane never applies to an 'Assign to: contact' step. create_deal config {pipelineId (required), stageId, dealName, dealValue, allowDuplicates, status, source, assignedTo, leaveUnassigned}: leaveUnassigned true leaves the card unassigned when no assignee is set (default false: the card goes to the lead's owner). close_other_open_deals config {lostReason (required)}: closes the contact's OTHER open cards as lost with that reason, keeping the run's own card (each moves to its pipeline's lost stage with the usual stage-change effects; cards already won/lost, in the DPP Post-Close pipeline or in a pipeline with no lost stage are skipped; a re-run closes nothing twice); it needs the run's own deal, so use a deal trigger or a find_opportunity step first.";
5
+ /** (deal nodes, app 10-13) find_opportunity v2, link_appointment_to_deal, update_deal_field systemField source; the catalog does not carry action config */
6
+ export declare const DEAL_NODES_ACTIONS_NOTE = " Deal nodes: find_opportunity config {version: 2, pipelineIds (ordered; the first pipeline holding a match wins; empty = every pipeline), status (open default | won | lost | abandoned | any), excludeSources (lead sources never matched, case-insensitive), excludeCreatedByAutomationIds (automations whose create_deal cards are skipped; cards created from 2026-10-13 on), matchField {fieldKey, value} (searches the WHOLE WORKSPACE, any contact, for the card whose deal custom field equals the value, merge fields allowed e.g. {{trigger.oes}}; a card is found when its field holds the searched value (trimmed) as the same text exactly (no case folding); a value written with separators such as dashes or spaces also matches its digits as text; it matches a stored number only when the value (or those digits) is a plain number with no leading zero and at most 15 significant digits that a number holds exactly (007 is text and does not match the number 7; a 16-digit value, 1e-400 or 3e-324 is text only); booleans and differently formatted stored values never match; status, pipelines and exclusions still apply; exactly one match is found, several are ambiguous and take not found; a found card becomes the run's contact when the run has none; the field must exist; an empty rendered value finds nothing), resolution}: searches the run's contact's cards; FOUND makes the card the run's deal and follows nextIfTrue (else next), NOT FOUND is a normal outcome that follows nextIfFalse (else next) and leaves the run's existing deal unchanged (a later deal step on the false branch still acts on the deal the run already had); without version 2 the step keeps the original single-pipeline lookup (a miss fails the step). Prerequisite: a version 2 find needs step cursors on this workspace, and publishing refuses (find_needs_step_cursors) with \"Find opportunity needs step cursors on this workspace; ask support\"; every version 2 find must hold its own live step cursor or it hard-stops the run without selecting or routing. A version 2 find cannot be retried on its own: retrying a failed one alone answers 409, so re-trigger the automation for the contact. An automation definition that touches deals needs the `deals` scope on an API key: without it the write answers 403. link_appointment_to_deal config {stageId, pipelineId (needs stageId), lostReason}: in an appointment-triggered run, links the trigger's appointment to the run's deal (its appointment fields follow it; a newer booking is never displaced; the appointment must be the deal's contact's), then optionally moves the card; it needs an appointment trigger and the run's deal (find_opportunity first, or a booking made with a deal id). update_deal_field config {systemField: \"source\", value, overwriteExisting}: sets the deal's own lead source instead of a custom field (a value only, trimmed, at most 500 characters; overwriteExisting false writes only while the source is the Unknown label).";
5
7
  /** (#124, automation unknown keys / types) the refusal codes the write paths answer; surfaced verbatim by the client (details JSON object last), never retried */
6
8
  export declare const AUTOMATION_KEY_REFUSALS_NOTE = " Unknown action keys are refused, never ignored: an action config key the action does not read (a typo such as `laneSoruce`) answers code `unknown_action_config_key` with the node, the key and, when one is close, a suggestion (\"Did you mean `laneSource`?\"); an action `type` no registry entry defines answers `unknown_action_type` (with 'Did you mean' when a registered type is close). Create and update answer 400 (an update that does not write `actions` is never refused for a key stored earlier), conduyt_workflow_validate answers 200 with valid false and the `errors` list (nodeId, field, code, message), and publish / a version restore answer 422 (the preflight refusal wraps `data.message`, `data.errors`, `data.warnings`). Read the suggestion, fix the key and send again; a stored workflow is never refused on read.";
7
9
  /** GET /automations/:id answers { data: { ...automation, configRevision } } (jsonResponse wraps): the revision lives under `data` */
@@ -2,6 +2,8 @@ import { z } from "zod";
2
2
  import { formatResult, formatToolError } from "../client.js";
3
3
  /** (card ownership per lane, app 10-08/09) the action options an agent can set; the catalog does not carry action config, so this is the one place they are documented */
4
4
  export const CARD_OWNERSHIP_ACTIONS_NOTE = " Card ownership per lane: assign_to_user config {userId | userIds (round robin) | groupId, target|applyTo: contact|deal|both, overwriteExisting, laneSource, lanePipelineId, laneStageId}: laneSource names a card lane (the Deal.source its Auto-Create step wrote, e.g. 'GSM', max 500 chars) and makes the deal leg assign the contact's newest OPEN card from that lane (inside lanePipelineId when set) instead of the run's deal; when the contact has none and lanePipelineId is set, the card is created there (laneStageId, else the pipeline's first stage) already owned by the user; lanePipelineId or laneStageId without laneSource, or laneStageId without lanePipelineId, is refused; a lane never applies to an 'Assign to: contact' step. create_deal config {pipelineId (required), stageId, dealName, dealValue, allowDuplicates, status, source, assignedTo, leaveUnassigned}: leaveUnassigned true leaves the card unassigned when no assignee is set (default false: the card goes to the lead's owner). close_other_open_deals config {lostReason (required)}: closes the contact's OTHER open cards as lost with that reason, keeping the run's own card (each moves to its pipeline's lost stage with the usual stage-change effects; cards already won/lost, in the DPP Post-Close pipeline or in a pipeline with no lost stage are skipped; a re-run closes nothing twice); it needs the run's own deal, so use a deal trigger or a find_opportunity step first.";
5
+ /** (deal nodes, app 10-13) find_opportunity v2, link_appointment_to_deal, update_deal_field systemField source; the catalog does not carry action config */
6
+ export const DEAL_NODES_ACTIONS_NOTE = " Deal nodes: find_opportunity config {version: 2, pipelineIds (ordered; the first pipeline holding a match wins; empty = every pipeline), status (open default | won | lost | abandoned | any), excludeSources (lead sources never matched, case-insensitive), excludeCreatedByAutomationIds (automations whose create_deal cards are skipped; cards created from 2026-10-13 on), matchField {fieldKey, value} (searches the WHOLE WORKSPACE, any contact, for the card whose deal custom field equals the value, merge fields allowed e.g. {{trigger.oes}}; a card is found when its field holds the searched value (trimmed) as the same text exactly (no case folding); a value written with separators such as dashes or spaces also matches its digits as text; it matches a stored number only when the value (or those digits) is a plain number with no leading zero and at most 15 significant digits that a number holds exactly (007 is text and does not match the number 7; a 16-digit value, 1e-400 or 3e-324 is text only); booleans and differently formatted stored values never match; status, pipelines and exclusions still apply; exactly one match is found, several are ambiguous and take not found; a found card becomes the run's contact when the run has none; the field must exist; an empty rendered value finds nothing), resolution}: searches the run's contact's cards; FOUND makes the card the run's deal and follows nextIfTrue (else next), NOT FOUND is a normal outcome that follows nextIfFalse (else next) and leaves the run's existing deal unchanged (a later deal step on the false branch still acts on the deal the run already had); without version 2 the step keeps the original single-pipeline lookup (a miss fails the step). Prerequisite: a version 2 find needs step cursors on this workspace, and publishing refuses (find_needs_step_cursors) with \"Find opportunity needs step cursors on this workspace; ask support\"; every version 2 find must hold its own live step cursor or it hard-stops the run without selecting or routing. A version 2 find cannot be retried on its own: retrying a failed one alone answers 409, so re-trigger the automation for the contact. An automation definition that touches deals needs the `deals` scope on an API key: without it the write answers 403. link_appointment_to_deal config {stageId, pipelineId (needs stageId), lostReason}: in an appointment-triggered run, links the trigger's appointment to the run's deal (its appointment fields follow it; a newer booking is never displaced; the appointment must be the deal's contact's), then optionally moves the card; it needs an appointment trigger and the run's deal (find_opportunity first, or a booking made with a deal id). update_deal_field config {systemField: \"source\", value, overwriteExisting}: sets the deal's own lead source instead of a custom field (a value only, trimmed, at most 500 characters; overwriteExisting false writes only while the source is the Unknown label).";
5
7
  /** (#124, automation unknown keys / types) the refusal codes the write paths answer; surfaced verbatim by the client (details JSON object last), never retried */
6
8
  export const AUTOMATION_KEY_REFUSALS_NOTE = " Unknown action keys are refused, never ignored: an action config key the action does not read (a typo such as `laneSoruce`) answers code `unknown_action_config_key` with the node, the key and, when one is close, a suggestion (\"Did you mean `laneSource`?\"); an action `type` no registry entry defines answers `unknown_action_type` (with 'Did you mean' when a registered type is close). Create and update answer 400 (an update that does not write `actions` is never refused for a key stored earlier), conduyt_workflow_validate answers 200 with valid false and the `errors` list (nodeId, field, code, message), and publish / a version restore answer 422 (the preflight refusal wraps `data.message`, `data.errors`, `data.warnings`). Read the suggestion, fix the key and send again; a stored workflow is never refused on read.";
7
9
  /** GET /automations/:id answers { data: { ...automation, configRevision } } (jsonResponse wraps): the revision lives under `data` */
@@ -36,7 +38,7 @@ export function registerAutomationTools(server, client) {
36
38
  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'."),
37
39
  n8nWorkflowId: z.string().optional().describe("n8n workflow ID (required when kind='n8n')"),
38
40
  n8nWebhookUrl: z.string().optional().describe("n8n webhook URL (required when kind='n8n')"),
39
- actions: z.any().optional().describe("Actions graph — v1: [{type, config}], v2: {nodes:[], startNodeId}. A sequence step is the action enroll_in_sequence with config {sequenceId, enrollAction: 'add' (default) | 'remove'}: add puts the contact into the sequence (the sequence must allow automation enrollment, allowAutomationEnroll), remove ends their run. A delay node waits for a fixed duration by default; waitType 'until_field_date' (with fieldKey = a deal DATE / DATETIME custom field key and offsetHours = signed hours added to its date, default 0, e.g. 24 = a day after it) waits until that date stored on the deal, re-read at every wake (an empty or past value continues immediately)." + CARD_OWNERSHIP_ACTIONS_NOTE + ""),
41
+ actions: z.any().optional().describe("Actions graph — v1: [{type, config}], v2: {nodes:[], startNodeId}. A sequence step is the action enroll_in_sequence with config {sequenceId, enrollAction: 'add' (default) | 'remove'}: add puts the contact into the sequence (the sequence must allow automation enrollment, allowAutomationEnroll), remove ends their run. A delay node waits for a fixed duration by default; waitType 'until_field_date' (with fieldKey = a deal DATE / DATETIME custom field key and offsetHours = signed hours added to its date, default 0, e.g. 24 = a day after it) waits until that date stored on the deal, re-read at every wake (an empty or past value continues immediately)." + CARD_OWNERSHIP_ACTIONS_NOTE + DEAL_NODES_ACTIONS_NOTE + ""),
40
42
  graphVersion: z.number().optional().describe("Graph version — 1 (legacy array) or 2 (node graph). Default 1"),
41
43
  schedule: z.string().optional().describe("5-field cron expression (required if triggerEvent='scheduled')"),
42
44
  scheduleTimezone: z.string().optional().describe("IANA timezone for scheduled triggers (e.g. 'America/Los_Angeles')"),
@@ -59,7 +61,7 @@ export function registerAutomationTools(server, client) {
59
61
  .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.)"),
60
62
  isActive: z.boolean().optional().describe("Enable or disable the automation"),
61
63
  expectedConfigRevision: z.number().int().nonnegative().optional().describe("REQUIRED whenever `actions` is replaced: the configRevision of the automation you read (conduyt_get_automation) before preparing the new graph. It is sent unchanged; if the automation was saved since, the API answers 409 with the current revision and nothing is overwritten, so re-read, re-apply your change and call again. Not needed for an update that leaves `actions` alone"),
62
- actions: z.any().optional().describe("Updated actions graph (same shape as create; e.g. enroll_in_sequence {sequenceId, enrollAction: 'add' | 'remove'}) A delay node waits for a fixed duration by default; waitType 'until_field_date' (with fieldKey = a deal DATE / DATETIME custom field key and offsetHours = signed hours added to its date, default 0, e.g. 24 = a day after it) waits until that date stored on the deal, re-read at every wake (an empty or past value continues immediately)." + CARD_OWNERSHIP_ACTIONS_NOTE + ""),
64
+ actions: z.any().optional().describe("Updated actions graph (same shape as create; e.g. enroll_in_sequence {sequenceId, enrollAction: 'add' | 'remove'}) A delay node waits for a fixed duration by default; waitType 'until_field_date' (with fieldKey = a deal DATE / DATETIME custom field key and offsetHours = signed hours added to its date, default 0, e.g. 24 = a day after it) waits until that date stored on the deal, re-read at every wake (an empty or past value continues immediately)." + CARD_OWNERSHIP_ACTIONS_NOTE + DEAL_NODES_ACTIONS_NOTE + ""),
63
65
  schedule: z.string().optional().describe("Updated cron schedule"),
64
66
  scheduleTimezone: z.string().optional().describe("Updated timezone"),
65
67
  stopOnResponse: z.boolean().optional().describe("Stop drip if contact responds"),
@@ -49,14 +49,16 @@ export function registerCalendarTools(server, client) {
49
49
  const result = await client.get(`/api/v1/appointments/${id}`);
50
50
  return formatResult(result);
51
51
  });
52
- server.tool("conduyt_create_appointment", "Create a new appointment. Times must include timezone offset (e.g. '2026-05-01T10:00:00-07:00').", {
52
+ server.tool("conduyt_create_appointment", "Create a new appointment. Times must include timezone offset (e.g. '2026-05-01T10:00:00-07:00'). "
53
+ + "contactId is looked up as a contact of your account first; when no contact has that id it is looked up as a deal (card), so a deal id may be passed there. dealId names the deal explicitly. A named deal must be open, in your account, and visible AND editable to the key (deals scope; seeing it is not enough): the appointment takes the deal's contact, is linked to the deal, and appointment.booked carries dealId; no other card is created. Errors: 400 deal_id_conflict / deal_contact_mismatch / invalid_deal_id / contact_not_found (contactId is neither a contact nor a deal you can use), 404 deal_not_found / deal_deleted, 403 deal_not_editable (you can see the deal but not change it), 409 deal_closed, 409 deal_contact_gone (the deal's contact was merged or deleted), 409 deal_changed (the deal moved to another contact while booking: retry the request).", {
53
54
  title: z.string().describe("Appointment title"),
54
55
  startTime: z.string().describe("Start time (ISO 8601 with timezone, e.g. '2026-05-01T10:00:00-07:00')"),
55
56
  endTime: z.string().describe("End time (ISO 8601 with timezone)"),
56
57
  description: z.string().optional().describe("Appointment description"),
57
58
  location: z.string().optional().describe("Location or meeting link"),
58
59
  calendarId: z.string().optional().describe("Calendar UUID"),
59
- contactId: z.string().optional().describe("Associated contact UUID"),
60
+ contactId: z.string().optional().describe("Associated contact UUID, or a deal UUID (looked up as a deal when no contact has it)"),
61
+ dealId: z.string().optional().describe("The deal (card) to book for: open, in your account, visible and editable to you; the appointment takes its contact and is linked to it"),
60
62
  assignedTo: z.string().optional().describe("Assigned team member UUID"),
61
63
  status: z.string().optional().describe("Status (e.g. 'confirmed', 'pending')"),
62
64
  notes: z.string().optional().describe("Internal notes"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.85.0",
3
+ "version": "4.86.0",
4
4
  "mcpName": "com.conduyt/mcp",
5
5
  "description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
6
6
  "type": "module",
@@ -1,11 +1,11 @@
1
1
  {
2
- "generatedAt": "2026-10-11T02:07:04.091Z",
2
+ "generatedAt": "2026-10-13T14:36:32.489Z",
3
3
  "toolCount": 350,
4
- "totalEndpoints": 1077,
4
+ "totalEndpoints": 1080,
5
5
  "counts": {
6
6
  "covered": 309,
7
7
  "excluded": 321,
8
- "planned": 447,
8
+ "planned": 450,
9
9
  "uncategorized": 0
10
10
  },
11
11
  "matrix": {
@@ -869,6 +869,10 @@
869
869
  "status": "planned",
870
870
  "reason": "booking page management"
871
871
  },
872
+ "POST /api/v1/booking-pages/:id/ical-feed/reveal": {
873
+ "status": "planned",
874
+ "reason": "booking page management"
875
+ },
872
876
  "GET /api/v1/booking-pages/:id/overrides": {
873
877
  "status": "planned",
874
878
  "reason": "booking page management"
@@ -1567,6 +1571,10 @@
1567
1571
  "conduyt_merge_contacts"
1568
1572
  ]
1569
1573
  },
1574
+ "GET /api/v1/contacts/score": {
1575
+ "status": "planned",
1576
+ "reason": "contact subroutes beyond the core CRUD/search tools"
1577
+ },
1570
1578
  "GET /api/v1/contacts/sources": {
1571
1579
  "status": "planned",
1572
1580
  "reason": "contact subroutes beyond the core CRUD/search tools"
@@ -2244,6 +2252,10 @@
2244
2252
  "conduyt_pipeline_health"
2245
2253
  ]
2246
2254
  },
2255
+ "GET /api/v1/deals/lead-id": {
2256
+ "status": "planned",
2257
+ "reason": "deal subroutes beyond core CRUD (history, files, links)"
2258
+ },
2247
2259
  "POST /api/v1/deals/probabilities": {
2248
2260
  "status": "planned",
2249
2261
  "reason": "deal subroutes beyond core CRUD (history, files, links)"