conduyt-mcp 4.11.0 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +50 -7
- package/dist/client.js +202 -40
- package/dist/index.js +6 -1
- package/dist/tools/ai-extended.js +2 -1
- package/dist/tools/bulk.js +14 -2
- package/dist/tools/privacy.js +6 -2
- package/dist/types.d.ts +6 -0
- package/package.json +3 -2
package/dist/client.d.ts
CHANGED
|
@@ -1,15 +1,58 @@
|
|
|
1
1
|
import type { ConduytConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Structured API error (SOL-017). `code` distinguishes transport-level
|
|
4
|
+
* failures (timeout / response_too_large / network) from HTTP failures
|
|
5
|
+
* (code "http", status set). `retryable` reflects whether the FAILURE class
|
|
6
|
+
* is transient — the client itself only ever retries idempotent GETs.
|
|
7
|
+
* The message keeps the legacy "Conduyt API <status>: <msg>" shape so
|
|
8
|
+
* existing tool-level catch blocks keep working.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ConduytApiError extends Error {
|
|
11
|
+
readonly status: number | null;
|
|
12
|
+
readonly code: "http" | "timeout" | "response_too_large" | "network";
|
|
13
|
+
readonly retryable: boolean;
|
|
14
|
+
constructor(opts: {
|
|
15
|
+
message: string;
|
|
16
|
+
status?: number | null;
|
|
17
|
+
code: ConduytApiError["code"];
|
|
18
|
+
retryable: boolean;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Per-call overrides for legitimately long/large operations (GDPR export,
|
|
23
|
+
* AI chat) — everything else rides the bounded defaults. */
|
|
24
|
+
export interface RequestOptions {
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
maxResponseBytes?: number;
|
|
27
|
+
}
|
|
2
28
|
export declare class ConduytClient {
|
|
3
29
|
private baseUrl;
|
|
4
30
|
private apiKey;
|
|
31
|
+
private timeoutMs;
|
|
32
|
+
private maxResponseBytes;
|
|
33
|
+
private retryBaseMs;
|
|
5
34
|
constructor(config: ConduytConfig);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
35
|
+
/**
|
|
36
|
+
* One bounded HTTP attempt: deadline via AbortSignal, size cap enforced
|
|
37
|
+
* from Content-Length before the body is read AND from the actual bytes
|
|
38
|
+
* after (chunked responses carry no length header).
|
|
39
|
+
*/
|
|
40
|
+
private attempt;
|
|
41
|
+
private httpError;
|
|
42
|
+
private retryDelayMs;
|
|
43
|
+
/**
|
|
44
|
+
* Retries are for idempotent GETs ONLY: transient statuses (429/502/503/
|
|
45
|
+
* 504), timeouts, and network errors. Mutations are never replayed — the
|
|
46
|
+
* server may have applied the write before the failure surfaced.
|
|
47
|
+
*/
|
|
48
|
+
private requestRaw;
|
|
49
|
+
request(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
50
|
+
requestText(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<string>;
|
|
51
|
+
get(path: string, opts?: RequestOptions): Promise<unknown>;
|
|
52
|
+
post(path: string, body: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
53
|
+
postText(path: string, body: unknown, opts?: RequestOptions): Promise<string>;
|
|
54
|
+
patch(path: string, body: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
55
|
+
del(path: string, opts?: RequestOptions): Promise<unknown>;
|
|
13
56
|
}
|
|
14
57
|
export declare function formatResult(data: unknown): {
|
|
15
58
|
content: Array<{
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured API error (SOL-017). `code` distinguishes transport-level
|
|
3
|
+
* failures (timeout / response_too_large / network) from HTTP failures
|
|
4
|
+
* (code "http", status set). `retryable` reflects whether the FAILURE class
|
|
5
|
+
* is transient — the client itself only ever retries idempotent GETs.
|
|
6
|
+
* The message keeps the legacy "Conduyt API <status>: <msg>" shape so
|
|
7
|
+
* existing tool-level catch blocks keep working.
|
|
8
|
+
*/
|
|
9
|
+
export class ConduytApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
code;
|
|
12
|
+
retryable;
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
15
|
+
this.name = "ConduytApiError";
|
|
16
|
+
this.status = opts.status ?? null;
|
|
17
|
+
this.code = opts.code;
|
|
18
|
+
this.retryable = opts.retryable;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Bounded defaults; each overridable via config / env (see src/index.ts). */
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
23
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 5_000_000;
|
|
24
|
+
const DEFAULT_RETRY_BASE_MS = 500;
|
|
25
|
+
const MAX_GET_RETRIES = 2;
|
|
26
|
+
const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
|
|
27
|
+
const MAX_RETRY_AFTER_MS = 10_000;
|
|
1
28
|
export class ConduytClient {
|
|
2
29
|
baseUrl;
|
|
3
30
|
apiKey;
|
|
31
|
+
timeoutMs;
|
|
32
|
+
maxResponseBytes;
|
|
33
|
+
retryBaseMs;
|
|
4
34
|
constructor(config) {
|
|
5
35
|
// Strip trailing slashes, then guard against a misconfigured base URL that
|
|
6
36
|
// already includes the API prefix. Every tool builds paths as `/api/v1/...`,
|
|
@@ -17,65 +47,197 @@ export class ConduytClient {
|
|
|
17
47
|
}
|
|
18
48
|
this.baseUrl = base;
|
|
19
49
|
this.apiKey = config.apiKey;
|
|
50
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
51
|
+
this.maxResponseBytes = config.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
52
|
+
this.retryBaseMs = config.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
20
53
|
}
|
|
21
|
-
|
|
54
|
+
/**
|
|
55
|
+
* One bounded HTTP attempt: deadline via AbortSignal, size cap enforced
|
|
56
|
+
* from Content-Length before the body is read AND from the actual bytes
|
|
57
|
+
* after (chunked responses carry no length header).
|
|
58
|
+
*/
|
|
59
|
+
async attempt(method, path, body, timeoutMs, maxResponseBytes) {
|
|
22
60
|
const url = `${this.baseUrl}${path}`;
|
|
23
61
|
const headers = {
|
|
24
62
|
Authorization: `Bearer ${this.apiKey}`,
|
|
25
63
|
"Content-Type": "application/json",
|
|
26
64
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
65
|
+
let res;
|
|
66
|
+
try {
|
|
67
|
+
res = await fetch(url, {
|
|
68
|
+
method,
|
|
69
|
+
headers,
|
|
70
|
+
body: body != null ? JSON.stringify(body) : undefined,
|
|
71
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
76
|
+
throw new ConduytApiError({
|
|
77
|
+
message: `Conduyt API timeout: no response within ${timeoutMs}ms for ${method} ${path}`,
|
|
78
|
+
code: "timeout",
|
|
79
|
+
retryable: true,
|
|
80
|
+
cause: err,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
throw new ConduytApiError({
|
|
84
|
+
message: `Conduyt API network error for ${method} ${path}: ${err?.message ?? String(err)}`,
|
|
85
|
+
code: "network",
|
|
86
|
+
retryable: true,
|
|
87
|
+
cause: err,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const tooLarge = (bytes) => new ConduytApiError({
|
|
91
|
+
message: `Conduyt API response too large (${bytes} bytes > ${maxResponseBytes} cap) for ${method} ${path}. Narrow the query or paginate.`,
|
|
92
|
+
status: res.status,
|
|
93
|
+
code: "response_too_large",
|
|
94
|
+
retryable: false,
|
|
31
95
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
throw
|
|
96
|
+
// Declared-size fast path (uncompressed responses)...
|
|
97
|
+
const declared = Number(res.headers.get("content-length") || 0);
|
|
98
|
+
if (declared > maxResponseBytes)
|
|
99
|
+
throw tooLarge(declared);
|
|
100
|
+
// ...then a STREAMED byte-accurate read: chunked/compressed responses
|
|
101
|
+
// carry no usable length header, and reading the whole body before
|
|
102
|
+
// checking would defeat the memory bound. The read stops (and the
|
|
103
|
+
// stream is cancelled) the moment the cap is crossed.
|
|
104
|
+
let text = "";
|
|
105
|
+
if (res.body) {
|
|
106
|
+
const reader = res.body.getReader();
|
|
107
|
+
const chunks = [];
|
|
108
|
+
let bytes = 0;
|
|
109
|
+
try {
|
|
110
|
+
for (;;) {
|
|
111
|
+
const { done, value } = await reader.read();
|
|
112
|
+
if (done)
|
|
113
|
+
break;
|
|
114
|
+
bytes += value.byteLength;
|
|
115
|
+
if (bytes > maxResponseBytes) {
|
|
116
|
+
await reader.cancel().catch(() => { });
|
|
117
|
+
throw tooLarge(bytes);
|
|
118
|
+
}
|
|
119
|
+
chunks.push(value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err instanceof ConduytApiError)
|
|
124
|
+
throw err;
|
|
125
|
+
throw new ConduytApiError({
|
|
126
|
+
message: `Conduyt API network error reading response for ${method} ${path}: ${err?.message ?? String(err)}`,
|
|
127
|
+
code: "network",
|
|
128
|
+
retryable: true,
|
|
129
|
+
cause: err,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const buf = new Uint8Array(bytes);
|
|
133
|
+
let off = 0;
|
|
134
|
+
for (const c of chunks) {
|
|
135
|
+
buf.set(c, off);
|
|
136
|
+
off += c.byteLength;
|
|
137
|
+
}
|
|
138
|
+
text = new TextDecoder().decode(buf);
|
|
36
139
|
}
|
|
37
|
-
|
|
140
|
+
else {
|
|
141
|
+
text = await res.text().catch(() => "");
|
|
142
|
+
if (text.length > maxResponseBytes)
|
|
143
|
+
throw tooLarge(text.length);
|
|
144
|
+
}
|
|
145
|
+
return { res, text };
|
|
38
146
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
147
|
+
httpError(res, text) {
|
|
148
|
+
let msg = res.statusText;
|
|
149
|
+
try {
|
|
150
|
+
const json = JSON.parse(text);
|
|
151
|
+
if (json?.error)
|
|
152
|
+
msg = json.error;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
if (text)
|
|
156
|
+
msg = text.slice(0, 500);
|
|
157
|
+
}
|
|
158
|
+
return new ConduytApiError({
|
|
159
|
+
message: `Conduyt API ${res.status}: ${msg}`,
|
|
160
|
+
status: res.status,
|
|
161
|
+
code: "http",
|
|
162
|
+
retryable: RETRYABLE_STATUSES.has(res.status),
|
|
49
163
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
164
|
+
}
|
|
165
|
+
retryDelayMs(res, attemptIndex) {
|
|
166
|
+
const retryAfter = Number(res?.headers.get("retry-after"));
|
|
167
|
+
if (Number.isFinite(retryAfter) && retryAfter >= 0) {
|
|
168
|
+
return Math.min(retryAfter * 1000, MAX_RETRY_AFTER_MS);
|
|
169
|
+
}
|
|
170
|
+
return Math.min(this.retryBaseMs * 2 ** attemptIndex, MAX_RETRY_AFTER_MS);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Retries are for idempotent GETs ONLY: transient statuses (429/502/503/
|
|
174
|
+
* 504), timeouts, and network errors. Mutations are never replayed — the
|
|
175
|
+
* server may have applied the write before the failure surfaced.
|
|
176
|
+
*/
|
|
177
|
+
async requestRaw(method, path, body, opts) {
|
|
178
|
+
const timeoutMs = opts?.timeoutMs ?? this.timeoutMs;
|
|
179
|
+
const maxResponseBytes = opts?.maxResponseBytes ?? this.maxResponseBytes;
|
|
180
|
+
const canRetry = method === "GET";
|
|
181
|
+
const attempts = canRetry ? MAX_GET_RETRIES + 1 : 1;
|
|
182
|
+
// TOTAL time budget across every attempt + backoff: 1.5x the per-call
|
|
183
|
+
// deadline. Without it, 3 stacked attempt timeouts + backoffs could hold
|
|
184
|
+
// an agent turn ~110s while the caller believed the bound was 30s.
|
|
185
|
+
const totalBudgetMs = Math.ceil(timeoutMs * 1.5);
|
|
186
|
+
const startedAt = Date.now();
|
|
187
|
+
let lastError = null;
|
|
188
|
+
for (let i = 0; i < attempts; i++) {
|
|
189
|
+
const remaining = totalBudgetMs - (Date.now() - startedAt);
|
|
190
|
+
let res = null;
|
|
53
191
|
try {
|
|
54
|
-
const
|
|
55
|
-
|
|
192
|
+
const out = await this.attempt(method, path, body, Math.max(1, Math.min(timeoutMs, remaining)), maxResponseBytes);
|
|
193
|
+
res = out.res;
|
|
194
|
+
if (out.res.ok)
|
|
195
|
+
return out.text;
|
|
196
|
+
lastError = this.httpError(out.res, out.text);
|
|
56
197
|
}
|
|
57
|
-
catch {
|
|
58
|
-
if (
|
|
59
|
-
|
|
198
|
+
catch (err) {
|
|
199
|
+
if (!(err instanceof ConduytApiError))
|
|
200
|
+
throw err;
|
|
201
|
+
if (err.code === "response_too_large")
|
|
202
|
+
throw err;
|
|
203
|
+
lastError = err;
|
|
60
204
|
}
|
|
61
|
-
|
|
205
|
+
const isLast = i === attempts - 1;
|
|
206
|
+
if (!canRetry || isLast || !lastError.retryable)
|
|
207
|
+
throw lastError;
|
|
208
|
+
const delay = this.retryDelayMs(res, i);
|
|
209
|
+
if (Date.now() - startedAt + delay >= totalBudgetMs)
|
|
210
|
+
throw lastError;
|
|
211
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
212
|
+
}
|
|
213
|
+
throw lastError;
|
|
214
|
+
}
|
|
215
|
+
async request(method, path, body, opts) {
|
|
216
|
+
const text = await this.requestRaw(method, path, body, opts);
|
|
217
|
+
try {
|
|
218
|
+
return JSON.parse(text);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
62
222
|
}
|
|
63
|
-
return text;
|
|
64
223
|
}
|
|
65
|
-
|
|
66
|
-
return this.
|
|
224
|
+
async requestText(method, path, body, opts) {
|
|
225
|
+
return this.requestRaw(method, path, body, opts);
|
|
226
|
+
}
|
|
227
|
+
get(path, opts) {
|
|
228
|
+
return this.request("GET", path, undefined, opts);
|
|
67
229
|
}
|
|
68
|
-
post(path, body) {
|
|
69
|
-
return this.request("POST", path, body);
|
|
230
|
+
post(path, body, opts) {
|
|
231
|
+
return this.request("POST", path, body, opts);
|
|
70
232
|
}
|
|
71
|
-
postText(path, body) {
|
|
72
|
-
return this.requestText("POST", path, body);
|
|
233
|
+
postText(path, body, opts) {
|
|
234
|
+
return this.requestText("POST", path, body, opts);
|
|
73
235
|
}
|
|
74
|
-
patch(path, body) {
|
|
75
|
-
return this.request("PATCH", path, body);
|
|
236
|
+
patch(path, body, opts) {
|
|
237
|
+
return this.request("PATCH", path, body, opts);
|
|
76
238
|
}
|
|
77
|
-
del(path) {
|
|
78
|
-
return this.request("DELETE", path);
|
|
239
|
+
del(path, opts) {
|
|
240
|
+
return this.request("DELETE", path, undefined, opts);
|
|
79
241
|
}
|
|
80
242
|
}
|
|
81
243
|
export function formatResult(data) {
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,12 @@ if (!apiKey.startsWith("cdy_")) {
|
|
|
47
47
|
console.error("CONDUYT_API_KEY must start with 'cdy_' — generate one at Settings > API Keys in Conduyt.");
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// Operational bounds (SOL-017): every HTTP call gets a deadline, a
|
|
51
|
+
// response-size cap, and conservative GET-only retries. Overridable via env
|
|
52
|
+
// for constrained runtimes.
|
|
53
|
+
const timeoutMs = Number(process.env.CONDUYT_TIMEOUT_MS) || undefined;
|
|
54
|
+
const maxResponseBytes = Number(process.env.CONDUYT_MAX_RESPONSE_BYTES) || undefined;
|
|
55
|
+
const client = new ConduytClient({ apiUrl, apiKey, timeoutMs, maxResponseBytes });
|
|
51
56
|
// Single source of truth for the version — read from package.json so the
|
|
52
57
|
// runtime initialize metadata can never drift from the published package.
|
|
53
58
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
@@ -93,7 +93,8 @@ export function registerAiExtendedTools(server, client) {
|
|
|
93
93
|
.optional()
|
|
94
94
|
.describe("Pin the chat to a specific record for grounded answers"),
|
|
95
95
|
}, async (params) => {
|
|
96
|
-
|
|
96
|
+
// A grounded multi-turn reply can stream well past the 30s default.
|
|
97
|
+
const streamText = await client.postText("/api/v1/ai/chat", params, { timeoutMs: 120_000 });
|
|
97
98
|
const reply = parseSseText(streamText);
|
|
98
99
|
return formatResult({ reply });
|
|
99
100
|
});
|
package/dist/tools/bulk.js
CHANGED
|
@@ -50,7 +50,15 @@ export function registerBulkTools(server, client) {
|
|
|
50
50
|
qp.set("created_to", params.created_to);
|
|
51
51
|
if (params.min_score)
|
|
52
52
|
qp.set("min_score", String(params.min_score));
|
|
53
|
-
|
|
53
|
+
// Full-book exports routinely exceed the 5MB default cap and the 30s
|
|
54
|
+
// deadline — widened, but bounded at 25MB: the payload is re-serialized
|
|
55
|
+
// into the tool result, so an unbounded budget would just move the
|
|
56
|
+
// failure into memory/model-context. Past 25MB the structured
|
|
57
|
+
// response_too_large error tells the agent to narrow the filters.
|
|
58
|
+
const result = await client.get(`/api/v1/contacts/export?${qp}`, {
|
|
59
|
+
timeoutMs: 120_000,
|
|
60
|
+
maxResponseBytes: 25_000_000,
|
|
61
|
+
});
|
|
54
62
|
return formatResult(result);
|
|
55
63
|
});
|
|
56
64
|
server.tool("conduyt_export_deals", "Export deals as JSON with filtering by status, pipeline, or search term.", {
|
|
@@ -65,7 +73,11 @@ export function registerBulkTools(server, client) {
|
|
|
65
73
|
qp.set("status", params.status);
|
|
66
74
|
if (params.pipeline)
|
|
67
75
|
qp.set("pipeline", params.pipeline);
|
|
68
|
-
|
|
76
|
+
// Same widened-but-bounded budget as the contacts export.
|
|
77
|
+
const result = await client.get(`/api/v1/deals/export?${qp}`, {
|
|
78
|
+
timeoutMs: 120_000,
|
|
79
|
+
maxResponseBytes: 25_000_000,
|
|
80
|
+
});
|
|
69
81
|
return formatResult(result);
|
|
70
82
|
});
|
|
71
83
|
server.tool("conduyt_bulk_tag_contacts", "Add one tag to many contacts in a single call. Requires contacts:edit.", {
|
package/dist/tools/privacy.js
CHANGED
|
@@ -14,7 +14,9 @@ export function registerPrivacyTools(server, client) {
|
|
|
14
14
|
// into a different subroute. Keep this export-only regardless of key role.
|
|
15
15
|
id: z.string().uuid().describe("Contact UUID to export"),
|
|
16
16
|
}, async ({ id }) => {
|
|
17
|
-
|
|
17
|
+
// A full portable export is legitimately big and slow on large
|
|
18
|
+
// tenants — wider per-call budget than the 30s/5MB defaults.
|
|
19
|
+
const result = await client.get(`/api/v1/contacts/${encodeURIComponent(id)}/gdpr-export`, { timeoutMs: 120_000, maxResponseBytes: 25_000_000 });
|
|
18
20
|
return formatResult(result);
|
|
19
21
|
});
|
|
20
22
|
server.tool("conduyt_forget_contact", "IRREVERSIBLE GDPR right-to-be-forgotten erasure: permanently anonymizes the contact and erases their personal data EVERYWHERE it appears — messages, notes, deals, files, automations, and exports — across the whole account. Suppression/consent tombstones are kept so they are not re-contacted. THERE IS NO UNDO. Requires an owner API key (the API enforces this) and an explicit confirm='FORGET'. On a large tenant this can legitimately take tens of seconds. A 409 is retryable (a concurrent merge or in-flight automation) — retry once.", {
|
|
@@ -25,7 +27,9 @@ export function registerPrivacyTools(server, client) {
|
|
|
25
27
|
.literal("FORGET")
|
|
26
28
|
.describe("Must be exactly the string 'FORGET'. This is a hard safety gate for an irreversible erasure — do not supply it unless the erasure is intended."),
|
|
27
29
|
}, async ({ id, confirm }) => {
|
|
28
|
-
|
|
30
|
+
// The tool doc promises "can take tens of seconds" on large tenants
|
|
31
|
+
// — the erasure must not be aborted by the 30s default deadline.
|
|
32
|
+
const result = await client.post(`/api/v1/contacts/${encodeURIComponent(id)}/gdpr-forget`, { confirm }, { timeoutMs: 300_000 });
|
|
29
33
|
return formatResult(result);
|
|
30
34
|
});
|
|
31
35
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export interface ConduytConfig {
|
|
2
2
|
apiUrl: string;
|
|
3
3
|
apiKey: string;
|
|
4
|
+
/** Per-call HTTP deadline in ms (default 30_000). Env: CONDUYT_TIMEOUT_MS. */
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
/** Response body cap in bytes (default 5_000_000). Env: CONDUYT_MAX_RESPONSE_BYTES. */
|
|
7
|
+
maxResponseBytes?: number;
|
|
8
|
+
/** Base backoff in ms for GET retries (default 500; tests use 1). */
|
|
9
|
+
retryBaseMs?: number;
|
|
4
10
|
}
|
|
5
11
|
export interface ApiResponse<T = unknown> {
|
|
6
12
|
data?: T;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conduyt-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"start": "tsx src/index.ts",
|
|
21
21
|
"build": "tsc",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
|
-
"
|
|
23
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
24
|
+
"prepublishOnly": "npm run check:version && npm run test && npm run build && ENFORCE=1 node scripts/verify-contracts.mjs",
|
|
24
25
|
"audit:contracts": "node scripts/verify-contracts.mjs",
|
|
25
26
|
"contracts:refresh": "node scripts/refresh-contracts.mjs",
|
|
26
27
|
"check:version": "node scripts/check-version-sync.mjs"
|