conduyt-mcp 4.73.0 → 4.74.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 CHANGED
@@ -152,6 +152,13 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
152
152
  | `conduyt_update_webhook` | Update URL, events, or enable/disable |
153
153
  | `conduyt_delete_webhook` | Delete a webhook endpoint |
154
154
 
155
+ ### WordPress forms
156
+ | Tool | Description |
157
+ |------|-------------|
158
+ | `conduyt_list_form_intakes` | List the intake addresses: every active one with its full URL, then the revoked history (newest 200) |
159
+ | `conduyt_create_form_intake` | Create an intake address for one Gravity Forms / WPForms / Elementor form (returns its full URL) |
160
+ | `conduyt_revoke_form_intake` | Revoke an intake address (the row is kept for history) |
161
+
155
162
  ### Notifications
156
163
  | Tool | Description |
157
164
  |------|-------------|
package/dist/client.d.ts CHANGED
@@ -11,12 +11,16 @@ export declare class ConduytApiError extends Error {
11
11
  readonly status: number | null;
12
12
  readonly code: "http" | "timeout" | "response_too_large" | "network";
13
13
  readonly retryable: boolean;
14
+ /** The response's Retry-After, in seconds, when it sent one (a busy 503 / a 429): surfaced to the agent, never used
15
+ * to replay a mutation. */
16
+ readonly retryAfterSeconds: number | null;
14
17
  constructor(opts: {
15
18
  message: string;
16
19
  status?: number | null;
17
20
  code: ConduytApiError["code"];
18
21
  retryable: boolean;
19
22
  cause?: unknown;
23
+ retryAfterSeconds?: number | null;
20
24
  });
21
25
  }
22
26
  /** Per-call overrides for legitimately long/large operations (GDPR export,
package/dist/client.js CHANGED
@@ -10,12 +10,16 @@ export class ConduytApiError extends Error {
10
10
  status;
11
11
  code;
12
12
  retryable;
13
+ /** The response's Retry-After, in seconds, when it sent one (a busy 503 / a 429): surfaced to the agent, never used
14
+ * to replay a mutation. */
15
+ retryAfterSeconds;
13
16
  constructor(opts) {
14
17
  super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
15
18
  this.name = "ConduytApiError";
16
19
  this.status = opts.status ?? null;
17
20
  this.code = opts.code;
18
21
  this.retryable = opts.retryable;
22
+ this.retryAfterSeconds = opts.retryAfterSeconds ?? null;
19
23
  }
20
24
  }
21
25
  /** Bounded defaults; each overridable via config / env (see src/index.ts). */
@@ -163,11 +167,14 @@ export class ConduytClient {
163
167
  if (text)
164
168
  msg = text.slice(0, 500);
165
169
  }
170
+ const retryAfterRaw = res.headers.get("retry-after");
171
+ const retryAfter = retryAfterRaw === null || retryAfterRaw.trim() === "" ? NaN : Number(retryAfterRaw);
166
172
  return new ConduytApiError({
167
173
  message: `Conduyt API ${res.status}: ${msg}`,
168
174
  status: res.status,
169
175
  code: "http",
170
176
  retryable: RETRYABLE_STATUSES.has(res.status),
177
+ retryAfterSeconds: Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null,
171
178
  });
172
179
  }
173
180
  retryDelayMs(res, attemptIndex) {
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { registerCalendarTools } from "./tools/calendar.js";
21
21
  import { registerFormTools } from "./tools/forms.js";
22
22
  import { registerInvoiceTools } from "./tools/invoices.js";
23
23
  import { registerWebhookTools } from "./tools/webhooks.js";
24
+ import { registerFormIntakeTools } from "./tools/form-intakes.js";
24
25
  import { registerNotificationTools } from "./tools/notifications.js";
25
26
  import { registerCustomFieldTools } from "./tools/custom-fields.js";
26
27
  import { registerSmartListTools } from "./tools/smart-lists.js";
@@ -93,6 +94,7 @@ registerCalendarTools(server, client);
93
94
  registerFormTools(server, client);
94
95
  registerInvoiceTools(server, client);
95
96
  registerWebhookTools(server, client);
97
+ registerFormIntakeTools(server, client);
96
98
  registerNotificationTools(server, client);
97
99
  registerCustomFieldTools(server, client);
98
100
  registerSmartListTools(server, client);
@@ -0,0 +1,22 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ /**
4
+ * WordPress forms intake addresses (app 10-07, tri-surface parity): a per-form URL that Gravity Forms (Webhooks
5
+ * Add-On), WPForms (Webhooks addon) or Elementor Pro (Webhook action) posts submissions to; each submission becomes a
6
+ * contact. GET / POST /api/v1/integrations/wordpress-forms/intakes, DELETE /api/v1/integrations/wordpress-forms/intakes/{id}.
7
+ * Listing and creating need the "form-intakes" AND "contacts" scopes on a write or admin tier key (the URLs are
8
+ * credentials that create and edit contacts); revoking needs "form-intakes". Create and list answer each active intake's
9
+ * full `url` (the credential, to paste into the form plugin); a revoked intake's `url` is null.
10
+ */
11
+ /** What each tool's key must hold: the API names only the FIRST missing scope, so a 403 is surfaced with all of it. */
12
+ export declare const LIST_AND_CREATE_REQUIREMENT = "the form-intakes AND contacts scopes on a write or admin tier key (a member: settings.edit, contacts.create and contacts.edit)";
13
+ export declare const REVOKE_REQUIREMENT = "the form-intakes scope on an admin tier key (a member: settings.edit)";
14
+ /** A 403 names the complete requirement; a busy 503 says when to retry (never replayed here: create is a mutation). */
15
+ export declare function intakeToolError(err: unknown, requirement: string): {
16
+ content: Array<{
17
+ type: "text";
18
+ text: string;
19
+ }>;
20
+ isError: true;
21
+ };
22
+ export declare function registerFormIntakeTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { ConduytApiError, formatResult, formatToolError } from "../client.js";
3
+ /**
4
+ * WordPress forms intake addresses (app 10-07, tri-surface parity): a per-form URL that Gravity Forms (Webhooks
5
+ * Add-On), WPForms (Webhooks addon) or Elementor Pro (Webhook action) posts submissions to; each submission becomes a
6
+ * contact. GET / POST /api/v1/integrations/wordpress-forms/intakes, DELETE /api/v1/integrations/wordpress-forms/intakes/{id}.
7
+ * Listing and creating need the "form-intakes" AND "contacts" scopes on a write or admin tier key (the URLs are
8
+ * credentials that create and edit contacts); revoking needs "form-intakes". Create and list answer each active intake's
9
+ * full `url` (the credential, to paste into the form plugin); a revoked intake's `url` is null.
10
+ */
11
+ /** What each tool's key must hold: the API names only the FIRST missing scope, so a 403 is surfaced with all of it. */
12
+ export const LIST_AND_CREATE_REQUIREMENT = "the form-intakes AND contacts scopes on a write or admin tier key (a member: settings.edit, contacts.create and contacts.edit)";
13
+ export const REVOKE_REQUIREMENT = "the form-intakes scope on an admin tier key (a member: settings.edit)";
14
+ /** A 403 names the complete requirement; a busy 503 says when to retry (never replayed here: create is a mutation). */
15
+ export function intakeToolError(err, requirement) {
16
+ if (err instanceof ConduytApiError) {
17
+ if (err.status === 403)
18
+ return formatToolError(`${err.message}. This tool needs ${requirement}.`);
19
+ if (err.status === 503 && err.retryAfterSeconds !== null) {
20
+ return formatToolError(`busy; retry after ${err.retryAfterSeconds} s (${err.message})`);
21
+ }
22
+ }
23
+ throw err;
24
+ }
25
+ export function registerFormIntakeTools(server, client) {
26
+ server.tool("conduyt_list_form_intakes", "List the account's WordPress form intake addresses: every active intake (newest first), then the revoked history (the newest 200 revocations): name, defaultTag, source, assignee, status, receivedCount, lastReceivedAt and `url`: the full address of every ACTIVE intake (the credential, to paste into the form plugin); null for a revoked one. Requires " +
27
+ LIST_AND_CREATE_REQUIREMENT +
28
+ " (the URLs are credentials that create and edit contacts).", {}, async () => {
29
+ try {
30
+ const result = await client.get("/api/v1/integrations/wordpress-forms/intakes");
31
+ return formatResult(result);
32
+ }
33
+ catch (err) {
34
+ return intakeToolError(err, LIST_AND_CREATE_REQUIREMENT);
35
+ }
36
+ });
37
+ server.tool("conduyt_create_form_intake", "Create a WordPress form intake address for one form. Answers the row with `url`, the full address (the credential; conduyt_list_form_intakes shows it too while the intake is active). Paste it into the form plugin's webhook URL (Gravity Forms Webhooks Add-On, WPForms Webhooks, Elementor Pro Webhook action). Each submission then creates or updates a contact (by email, then phone), applies defaultTag / source / assignedTo, and adds 'Submitted <form name>' with the message and fields to the contact's timeline. At most 100 active addresses per account; a busy answer (503) says how many seconds to wait before retrying. Requires " +
38
+ LIST_AND_CREATE_REQUIREMENT +
39
+ ".", {
40
+ name: z.string().trim().min(1).max(120).describe("What the form is, e.g. 'Contact page, Smith Roofing'"),
41
+ defaultTag: z.string().max(100).nullable().optional().describe("Tag NAME applied to every submission unless the submission sends its own tags (created on first use)"),
42
+ source: z.string().max(64).optional().describe("Contact source for submissions that do not send one (default 'wordpress')"),
43
+ assignedTo: z.string().uuid().nullable().optional().describe("User id (an active member) who owns new contacts; omit or null = lead routing decides"),
44
+ }, async ({ name, defaultTag, source, assignedTo }) => {
45
+ try {
46
+ // the body as ONE literal: the contract gate reads the request fields from it
47
+ const result = await client.post("/api/v1/integrations/wordpress-forms/intakes", {
48
+ name,
49
+ ...(defaultTag !== undefined ? { defaultTag } : {}),
50
+ ...(source !== undefined ? { source } : {}),
51
+ ...(assignedTo !== undefined ? { assignedTo } : {}),
52
+ });
53
+ return formatResult(result);
54
+ }
55
+ catch (err) {
56
+ return intakeToolError(err, LIST_AND_CREATE_REQUIREMENT);
57
+ }
58
+ });
59
+ server.tool("conduyt_revoke_form_intake", "Revoke a WordPress form intake address: submissions to it answer 404 from now on and create nothing. The row is kept (status 'revoked', revokedAt) for history; revoking a revoked address answers it unchanged. Requires " +
60
+ REVOKE_REQUIREMENT +
61
+ ".", {
62
+ id: z.string().uuid().describe("Intake UUID (from conduyt_list_form_intakes)"),
63
+ }, async ({ id }) => {
64
+ try {
65
+ const result = await client.del(`/api/v1/integrations/wordpress-forms/intakes/${id}`);
66
+ return formatResult(result);
67
+ }
68
+ catch (err) {
69
+ return intakeToolError(err, REVOKE_REQUIREMENT);
70
+ }
71
+ });
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.73.0",
3
+ "version": "4.74.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,10 +1,10 @@
1
1
  {
2
- "generatedAt": "2026-10-04T13:54:51.239Z",
3
- "toolCount": 299,
4
- "totalEndpoints": 1043,
2
+ "generatedAt": "2026-10-07T06:35:55.228Z",
3
+ "toolCount": 302,
4
+ "totalEndpoints": 1047,
5
5
  "counts": {
6
- "covered": 273,
7
- "excluded": 317,
6
+ "covered": 276,
7
+ "excluded": 318,
8
8
  "planned": 453,
9
9
  "uncategorized": 0
10
10
  },
@@ -2997,6 +2997,24 @@
2997
2997
  "status": "planned",
2998
2998
  "reason": "integration management"
2999
2999
  },
3000
+ "GET /api/v1/integrations/wordpress-forms/intakes": {
3001
+ "status": "covered",
3002
+ "tools": [
3003
+ "conduyt_list_form_intakes"
3004
+ ]
3005
+ },
3006
+ "POST /api/v1/integrations/wordpress-forms/intakes": {
3007
+ "status": "covered",
3008
+ "tools": [
3009
+ "conduyt_create_form_intake"
3010
+ ]
3011
+ },
3012
+ "DELETE /api/v1/integrations/wordpress-forms/intakes/:id": {
3013
+ "status": "covered",
3014
+ "tools": [
3015
+ "conduyt_revoke_form_intake"
3016
+ ]
3017
+ },
3000
3018
  "GET /api/v1/integrations/zapier/sample/:event": {
3001
3019
  "status": "planned",
3002
3020
  "reason": "integration management"
@@ -4577,6 +4595,10 @@
4577
4595
  "status": "excluded",
4578
4596
  "reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
4579
4597
  },
4598
+ "POST /api/v1/webhooks/inbound/forms/:token": {
4599
+ "status": "excluded",
4600
+ "reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
4601
+ },
4580
4602
  "GET /api/v1/webhooks/manage": {
4581
4603
  "status": "covered",
4582
4604
  "tools": [