capsulemcp 1.6.0 → 1.6.2
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 -1
- package/dist/http.js +586 -557
- package/dist/index.js +586 -557
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -304,11 +304,22 @@ async function doFetch(url, options) {
|
|
|
304
304
|
"Rate limit exceeded after one retry. Please slow down your requests."
|
|
305
305
|
);
|
|
306
306
|
}
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
return { ...retried, startedAt, method, url, retriedAfter429: true };
|
|
308
|
+
}
|
|
309
|
+
return { ...first, startedAt, method, url, retriedAfter429: false };
|
|
310
|
+
}
|
|
311
|
+
async function consumeBody(start, body) {
|
|
312
|
+
try {
|
|
313
|
+
return await body();
|
|
314
|
+
} finally {
|
|
315
|
+
emitCapsuleRequest(
|
|
316
|
+
start.method,
|
|
317
|
+
start.url,
|
|
318
|
+
start.res,
|
|
319
|
+
Date.now() - start.startedAt,
|
|
320
|
+
start.retriedAfter429
|
|
321
|
+
);
|
|
309
322
|
}
|
|
310
|
-
emitCapsuleRequest(method, url, first.res, Date.now() - startedAt, false);
|
|
311
|
-
return first;
|
|
312
323
|
}
|
|
313
324
|
function emitCapsuleRequest(method, url, res, durationMs, retriedAfter429) {
|
|
314
325
|
let path = "";
|
|
@@ -358,13 +369,15 @@ function buildUrl(path, params) {
|
|
|
358
369
|
async function capsuleGet(path, params) {
|
|
359
370
|
const token = getToken();
|
|
360
371
|
const url = buildUrl(path, params);
|
|
361
|
-
const
|
|
372
|
+
const start = await doFetch(url, { headers: baseHeaders(token) });
|
|
362
373
|
try {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
374
|
+
return await consumeBody(start, async () => {
|
|
375
|
+
const data = await handleResponse(start.res);
|
|
376
|
+
const nextPage = parseNextPage(start.res.headers.get("Link"));
|
|
377
|
+
return { data, nextPage };
|
|
378
|
+
});
|
|
366
379
|
} finally {
|
|
367
|
-
cleanup();
|
|
380
|
+
start.cleanup();
|
|
368
381
|
}
|
|
369
382
|
}
|
|
370
383
|
async function capsuleGetCached(path, params) {
|
|
@@ -399,123 +412,130 @@ async function capsulePost(path, body) {
|
|
|
399
412
|
if (isReadOnly()) throw new CapsuleReadOnlyError("POST");
|
|
400
413
|
const token = getToken();
|
|
401
414
|
const url = buildUrl(path);
|
|
402
|
-
const
|
|
415
|
+
const start = await doFetch(url, {
|
|
403
416
|
method: "POST",
|
|
404
417
|
headers: { ...baseHeaders(token), "Content-Type": "application/json" },
|
|
405
418
|
body: JSON.stringify(body)
|
|
406
419
|
});
|
|
407
420
|
try {
|
|
408
|
-
return await handleResponse(res);
|
|
421
|
+
return await consumeBody(start, () => handleResponse(start.res));
|
|
409
422
|
} finally {
|
|
410
|
-
cleanup();
|
|
423
|
+
start.cleanup();
|
|
411
424
|
}
|
|
412
425
|
}
|
|
413
426
|
async function capsulePostNoContent(path) {
|
|
414
427
|
if (isReadOnly()) throw new CapsuleReadOnlyError("POST");
|
|
415
428
|
const token = getToken();
|
|
416
429
|
const url = buildUrl(path);
|
|
417
|
-
const
|
|
430
|
+
const start = await doFetch(url, {
|
|
418
431
|
method: "POST",
|
|
419
432
|
headers: baseHeaders(token)
|
|
420
433
|
});
|
|
421
434
|
try {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
435
|
+
await consumeBody(start, async () => {
|
|
436
|
+
if (start.res.status === 204) return;
|
|
437
|
+
await throwForStatus(start.res);
|
|
438
|
+
await mapAbort(start.res.text());
|
|
439
|
+
});
|
|
425
440
|
} finally {
|
|
426
|
-
cleanup();
|
|
441
|
+
start.cleanup();
|
|
427
442
|
}
|
|
428
443
|
}
|
|
429
444
|
async function capsuleSearch(path, body, params) {
|
|
430
445
|
const token = getToken();
|
|
431
446
|
const url = buildUrl(path, params);
|
|
432
|
-
const
|
|
447
|
+
const start = await doFetch(url, {
|
|
433
448
|
method: "POST",
|
|
434
449
|
headers: { ...baseHeaders(token), "Content-Type": "application/json" },
|
|
435
450
|
body: JSON.stringify(body)
|
|
436
451
|
});
|
|
437
452
|
try {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
453
|
+
return await consumeBody(start, async () => {
|
|
454
|
+
const data = await handleResponse(start.res);
|
|
455
|
+
const nextPage = parseNextPage(start.res.headers.get("Link"));
|
|
456
|
+
return { data, nextPage };
|
|
457
|
+
});
|
|
441
458
|
} finally {
|
|
442
|
-
cleanup();
|
|
459
|
+
start.cleanup();
|
|
443
460
|
}
|
|
444
461
|
}
|
|
445
462
|
async function capsulePut(path, body) {
|
|
446
463
|
if (isReadOnly()) throw new CapsuleReadOnlyError("PUT");
|
|
447
464
|
const token = getToken();
|
|
448
465
|
const url = buildUrl(path);
|
|
449
|
-
const
|
|
466
|
+
const start = await doFetch(url, {
|
|
450
467
|
method: "PUT",
|
|
451
468
|
headers: { ...baseHeaders(token), "Content-Type": "application/json" },
|
|
452
469
|
body: JSON.stringify(body)
|
|
453
470
|
});
|
|
454
471
|
try {
|
|
455
|
-
return await handleResponse(res);
|
|
472
|
+
return await consumeBody(start, () => handleResponse(start.res));
|
|
456
473
|
} finally {
|
|
457
|
-
cleanup();
|
|
474
|
+
start.cleanup();
|
|
458
475
|
}
|
|
459
476
|
}
|
|
460
477
|
async function capsuleGetBinary(path, maxBytes) {
|
|
461
478
|
const token = getToken();
|
|
462
479
|
const url = buildUrl(path);
|
|
463
|
-
const
|
|
480
|
+
const start = await doFetch(url, { headers: baseHeaders(token) });
|
|
464
481
|
try {
|
|
465
|
-
await
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
buffer: Buffer.alloc(0),
|
|
475
|
-
truncated: true,
|
|
476
|
-
sizeBytes: declaredBytes
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
if (maxBytes !== void 0 && res.body) {
|
|
480
|
-
const reader = res.body.getReader();
|
|
481
|
-
const chunks = [];
|
|
482
|
-
let total = 0;
|
|
483
|
-
let truncated = false;
|
|
484
|
-
while (true) {
|
|
485
|
-
const { done, value } = await mapAbort(reader.read());
|
|
486
|
-
if (done) break;
|
|
487
|
-
total += value.byteLength;
|
|
488
|
-
if (total > maxBytes) {
|
|
489
|
-
truncated = true;
|
|
490
|
-
await reader.cancel().catch(() => {
|
|
491
|
-
});
|
|
492
|
-
break;
|
|
493
|
-
}
|
|
494
|
-
chunks.push(value);
|
|
495
|
-
}
|
|
496
|
-
if (truncated) {
|
|
482
|
+
return await consumeBody(start, async () => {
|
|
483
|
+
const res = start.res;
|
|
484
|
+
await throwForStatus(res);
|
|
485
|
+
const contentType = res.headers.get("Content-Type") ?? "application/octet-stream";
|
|
486
|
+
const declared = res.headers.get("Content-Length");
|
|
487
|
+
const declaredBytes = declared ? Number(declared) : NaN;
|
|
488
|
+
if (maxBytes !== void 0 && Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
489
|
+
if (res.body) await res.body.cancel().catch(() => {
|
|
490
|
+
});
|
|
497
491
|
return {
|
|
498
492
|
contentType,
|
|
499
493
|
buffer: Buffer.alloc(0),
|
|
500
494
|
truncated: true,
|
|
501
|
-
sizeBytes:
|
|
495
|
+
sizeBytes: declaredBytes
|
|
502
496
|
};
|
|
503
497
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
498
|
+
if (maxBytes !== void 0 && res.body) {
|
|
499
|
+
const reader = res.body.getReader();
|
|
500
|
+
const chunks = [];
|
|
501
|
+
let total = 0;
|
|
502
|
+
let truncated = false;
|
|
503
|
+
while (true) {
|
|
504
|
+
const { done, value } = await mapAbort(reader.read());
|
|
505
|
+
if (done) break;
|
|
506
|
+
total += value.byteLength;
|
|
507
|
+
if (total > maxBytes) {
|
|
508
|
+
truncated = true;
|
|
509
|
+
await reader.cancel().catch(() => {
|
|
510
|
+
});
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
chunks.push(value);
|
|
514
|
+
}
|
|
515
|
+
if (truncated) {
|
|
516
|
+
return {
|
|
517
|
+
contentType,
|
|
518
|
+
buffer: Buffer.alloc(0),
|
|
519
|
+
truncated: true,
|
|
520
|
+
sizeBytes: total
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
const buffer2 = Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
|
524
|
+
return { contentType, buffer: buffer2, sizeBytes: buffer2.length };
|
|
525
|
+
}
|
|
526
|
+
const arrayBuffer = await mapAbort(res.arrayBuffer());
|
|
527
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
528
|
+
return { contentType, buffer, sizeBytes: buffer.length };
|
|
529
|
+
});
|
|
510
530
|
} finally {
|
|
511
|
-
cleanup();
|
|
531
|
+
start.cleanup();
|
|
512
532
|
}
|
|
513
533
|
}
|
|
514
534
|
async function capsulePostBinary(path, body, contentType, filename) {
|
|
515
535
|
if (isReadOnly()) throw new CapsuleReadOnlyError("POST");
|
|
516
536
|
const token = getToken();
|
|
517
537
|
const url = buildUrl(path);
|
|
518
|
-
const
|
|
538
|
+
const start = await doFetch(url, {
|
|
519
539
|
method: "POST",
|
|
520
540
|
headers: {
|
|
521
541
|
...baseHeaders(token),
|
|
@@ -526,25 +546,27 @@ async function capsulePostBinary(path, body, contentType, filename) {
|
|
|
526
546
|
body
|
|
527
547
|
});
|
|
528
548
|
try {
|
|
529
|
-
return await handleResponse(res);
|
|
549
|
+
return await consumeBody(start, () => handleResponse(start.res));
|
|
530
550
|
} finally {
|
|
531
|
-
cleanup();
|
|
551
|
+
start.cleanup();
|
|
532
552
|
}
|
|
533
553
|
}
|
|
534
554
|
async function capsuleDelete(path) {
|
|
535
555
|
if (isReadOnly()) throw new CapsuleReadOnlyError("DELETE");
|
|
536
556
|
const token = getToken();
|
|
537
557
|
const url = buildUrl(path);
|
|
538
|
-
const
|
|
558
|
+
const start = await doFetch(url, {
|
|
539
559
|
method: "DELETE",
|
|
540
560
|
headers: baseHeaders(token)
|
|
541
561
|
});
|
|
542
562
|
try {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
563
|
+
await consumeBody(start, async () => {
|
|
564
|
+
if (start.res.status === 204) return;
|
|
565
|
+
await throwForStatus(start.res);
|
|
566
|
+
await mapAbort(start.res.text());
|
|
567
|
+
});
|
|
546
568
|
} finally {
|
|
547
|
-
cleanup();
|
|
569
|
+
start.cleanup();
|
|
548
570
|
}
|
|
549
571
|
}
|
|
550
572
|
|
|
@@ -1352,18 +1374,19 @@ function registerToolTask(server, name, description, schema, handler) {
|
|
|
1352
1374
|
}
|
|
1353
1375
|
|
|
1354
1376
|
// src/tools/parties.ts
|
|
1355
|
-
import { z as
|
|
1377
|
+
import { z as z7 } from "zod";
|
|
1356
1378
|
|
|
1357
|
-
// src/tools/
|
|
1358
|
-
|
|
1359
|
-
|
|
1379
|
+
// src/tools/body-helpers.ts
|
|
1380
|
+
function setRef(body, key, id) {
|
|
1381
|
+
if (id) body[key] = { id };
|
|
1382
|
+
}
|
|
1383
|
+
function setNullableRef(body, key, id) {
|
|
1384
|
+
if (id === null) body[key] = null;
|
|
1385
|
+
else if (id !== void 0) body[key] = { id };
|
|
1386
|
+
}
|
|
1360
1387
|
|
|
1361
|
-
// src/tools/
|
|
1388
|
+
// src/tools/define-batch.ts
|
|
1362
1389
|
import { z as z2 } from "zod";
|
|
1363
|
-
var CONFIRM_REQUIRED_MESSAGE = "confirm: true is required to perform this destructive operation (set the parameter explicitly to acknowledge the destructive intent)";
|
|
1364
|
-
function confirmFlag() {
|
|
1365
|
-
return z2.literal(true, { error: () => CONFIRM_REQUIRED_MESSAGE });
|
|
1366
|
-
}
|
|
1367
1390
|
|
|
1368
1391
|
// src/capsule/batch.ts
|
|
1369
1392
|
function chunk(arr, size) {
|
|
@@ -1457,6 +1480,39 @@ function topFailureReasons(results, n) {
|
|
|
1457
1480
|
return Array.from(counts.values()).sort((a, b) => b.count - a.count).slice(0, n);
|
|
1458
1481
|
}
|
|
1459
1482
|
|
|
1483
|
+
// src/tools/define-batch.ts
|
|
1484
|
+
function defineBatch(args) {
|
|
1485
|
+
const schema = z2.object({
|
|
1486
|
+
items: z2.array(args.itemSchema).min(1).max(50).describe(args.itemDescription)
|
|
1487
|
+
});
|
|
1488
|
+
async function handler(input, opts = {}) {
|
|
1489
|
+
return batchExecute(args.toolName, input.items, args.itemHandler, opts);
|
|
1490
|
+
}
|
|
1491
|
+
return { schema, handler };
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/tools/descriptions.ts
|
|
1495
|
+
var EMBED_TAGS_FIELDS_DESCRIPTION = "Comma-separated embeds, e.g. 'tags,fields'";
|
|
1496
|
+
var EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION = "Comma-separated embeds, e.g. 'attachments,participants'";
|
|
1497
|
+
|
|
1498
|
+
// src/tools/define-delete.ts
|
|
1499
|
+
import { z as z5 } from "zod";
|
|
1500
|
+
|
|
1501
|
+
// src/tools/confirm-flag.ts
|
|
1502
|
+
import { z as z3 } from "zod";
|
|
1503
|
+
var CONFIRM_REQUIRED_MESSAGE = "confirm: true is required to perform this destructive operation (set the parameter explicitly to acknowledge the destructive intent)";
|
|
1504
|
+
function confirmFlag() {
|
|
1505
|
+
return z3.literal(true, { error: () => CONFIRM_REQUIRED_MESSAGE });
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// src/tools/shared-schemas.ts
|
|
1509
|
+
import { z as z4 } from "zod";
|
|
1510
|
+
var positiveId = z4.preprocess((input) => {
|
|
1511
|
+
if (typeof input !== "string") return input;
|
|
1512
|
+
const trimmed = input.trim();
|
|
1513
|
+
return /^\d+$/.test(trimmed) ? Number(trimmed) : input;
|
|
1514
|
+
}, z4.number().int().positive());
|
|
1515
|
+
|
|
1460
1516
|
// src/capsule/idempotent.ts
|
|
1461
1517
|
var isCapsule404 = (err) => err instanceof CapsuleApiError && err.status === 404;
|
|
1462
1518
|
var isCapsuleTagNotFound = (err) => err instanceof CapsuleApiError && err.status === 422 && /tag not found/i.test(err.message);
|
|
@@ -1479,13 +1535,33 @@ async function idempotentWithResult(op, success, alreadyDone, isAlreadyDoneError
|
|
|
1479
1535
|
}
|
|
1480
1536
|
}
|
|
1481
1537
|
|
|
1538
|
+
// src/tools/define-delete.ts
|
|
1539
|
+
function defineDelete(args) {
|
|
1540
|
+
const { toolName, pathPrefix, confirmHint, idDescription } = args;
|
|
1541
|
+
const schema = z5.object({
|
|
1542
|
+
id: idDescription ? positiveId.describe(idDescription) : positiveId,
|
|
1543
|
+
confirm: confirmFlag().describe(confirmHint)
|
|
1544
|
+
});
|
|
1545
|
+
async function handler(input) {
|
|
1546
|
+
if (input.confirm !== true) {
|
|
1547
|
+
throw new Error(`${toolName} requires confirm: true`);
|
|
1548
|
+
}
|
|
1549
|
+
return idempotent(
|
|
1550
|
+
() => capsuleDelete(`${pathPrefix}/${input.id}`),
|
|
1551
|
+
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1552
|
+
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
return { schema, handler };
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1482
1558
|
// src/tools/custom-field-helpers.ts
|
|
1483
|
-
import { z as
|
|
1484
|
-
var CustomFieldWriteSchema =
|
|
1485
|
-
definitionId:
|
|
1559
|
+
import { z as z6 } from "zod";
|
|
1560
|
+
var CustomFieldWriteSchema = z6.object({
|
|
1561
|
+
definitionId: positiveId.describe(
|
|
1486
1562
|
"The custom-field definition id from list_custom_fields. Identifies which field on the entity to set."
|
|
1487
1563
|
),
|
|
1488
|
-
value:
|
|
1564
|
+
value: z6.union([z6.string(), z6.number(), z6.boolean(), z6.null()]).describe(
|
|
1489
1565
|
"The new value. String for TEXT / DATE / LIST / LARGE_TEXT / LINK fields, number for NUMBER fields, boolean for BOOLEAN fields. Clearing: pass null for TEXT / NUMBER / DATE / LIST (Capsule removes the row). BOOLEAN does NOT accept null (Capsule returns 422 'invalid type for field'); use `value: false` instead. Note BOOLEAN fields are observably **two-state**: a row exists with `value: true`, or no row exists. Setting `value: false` removes the row entirely \u2014 readers should treat absent BOOLEAN rows as equivalent to false. Tri-state BOOLEAN semantics (true / false / unknown) are not achievable through Capsule's API. Audit-log noise: sending value=null on a field that's already empty/cleared is accepted by Capsule but still bumps the parent entity's `updatedAt`. Read the current value via embed='fields' first if `updatedAt` is being used as a 'last meaningful change' signal. NUMBER quirks: Capsule stores numerics correctly but the read-back via embed=fields returns them as STRINGS (e.g. value=3 reads as '3'); callers comparing values must coerce. TEXT quirks: value='' has the same observable effect as value=null (row removed); empty-string and never-set are indistinguishable."
|
|
1490
1566
|
)
|
|
1491
1567
|
});
|
|
@@ -1501,24 +1577,24 @@ function mapFieldsForBody(fields) {
|
|
|
1501
1577
|
}
|
|
1502
1578
|
|
|
1503
1579
|
// src/tools/parties.ts
|
|
1504
|
-
var EmailAddressSchema =
|
|
1505
|
-
address:
|
|
1506
|
-
type:
|
|
1580
|
+
var EmailAddressSchema = z7.object({
|
|
1581
|
+
address: z7.string().email(),
|
|
1582
|
+
type: z7.string().optional()
|
|
1507
1583
|
});
|
|
1508
|
-
var PhoneNumberSchema =
|
|
1584
|
+
var PhoneNumberSchema = z7.object({
|
|
1509
1585
|
// Capsule rejects empty strings with `phoneNumber.number: number is
|
|
1510
1586
|
// required`. Enforce at the schema layer to catch typos pre-call,
|
|
1511
1587
|
// matching how EmailAddressSchema's address field behaves.
|
|
1512
|
-
number:
|
|
1513
|
-
type:
|
|
1588
|
+
number: z7.string().min(1),
|
|
1589
|
+
type: z7.string().optional()
|
|
1514
1590
|
});
|
|
1515
1591
|
var CountryDescription = "Country name. Capsule validates this against a small canonical-English-name dictionary; inputs not in the dictionary are REJECTED with 422 'address.country: unknown country' (NOT silently passed through or normalised). Probed examples \u2014 accepted: `United States`, `United Kingdom`, `Czechia`, `Germany`. Aliased: `USA \u2192 United States`. Rejected: `United States of America`, `Czech Republic` (use `Czechia`), `UK`/`Britain` (use `United Kingdom`), `Deutschland` (use `Germany`). Empty string is accepted and stored as `null` \u2014 a de-facto 'clear' shape. To discover an accepted name, read an existing party that already has the country set.";
|
|
1516
|
-
var AddressSchema =
|
|
1517
|
-
street:
|
|
1518
|
-
city:
|
|
1519
|
-
state:
|
|
1520
|
-
country:
|
|
1521
|
-
zip:
|
|
1592
|
+
var AddressSchema = z7.object({
|
|
1593
|
+
street: z7.string().optional(),
|
|
1594
|
+
city: z7.string().optional(),
|
|
1595
|
+
state: z7.string().optional(),
|
|
1596
|
+
country: z7.string().optional().describe(CountryDescription),
|
|
1597
|
+
zip: z7.string().optional()
|
|
1522
1598
|
});
|
|
1523
1599
|
function validateWebsiteAddress(data, ctx) {
|
|
1524
1600
|
const isUrlService = data.service === void 0 || data.service === "URL";
|
|
@@ -1541,7 +1617,7 @@ function validateWebsiteAddress(data, ctx) {
|
|
|
1541
1617
|
});
|
|
1542
1618
|
}
|
|
1543
1619
|
}
|
|
1544
|
-
var WebsiteServiceEnum =
|
|
1620
|
+
var WebsiteServiceEnum = z7.enum([
|
|
1545
1621
|
"URL",
|
|
1546
1622
|
"SKYPE",
|
|
1547
1623
|
"TWITTER",
|
|
@@ -1560,19 +1636,19 @@ var WebsiteServiceEnum = z4.enum([
|
|
|
1560
1636
|
"BLUESKY",
|
|
1561
1637
|
"SNAPCHAT"
|
|
1562
1638
|
]);
|
|
1563
|
-
var WebsiteSchema =
|
|
1564
|
-
address:
|
|
1639
|
+
var WebsiteSchema = z7.object({
|
|
1640
|
+
address: z7.string().min(1).describe(
|
|
1565
1641
|
"The website address. A URL when service='URL', or a handle (e.g. '@acmeco') for social services like 'TWITTER', 'INSTAGRAM'. Capsule names this field `address` regardless of service type."
|
|
1566
1642
|
),
|
|
1567
1643
|
service: WebsiteServiceEnum.optional().describe(
|
|
1568
1644
|
"Service type. One of: URL, SKYPE, TWITTER, LINKED_IN, FACEBOOK, XING, FEED, GOOGLE_PLUS, FLICKR, GITHUB, YOUTUBE, INSTAGRAM, PINTEREST, TIKTOK, THREADS, BLUESKY, SNAPCHAT. Defaults to 'URL' if omitted."
|
|
1569
1645
|
)
|
|
1570
1646
|
}).superRefine(validateWebsiteAddress);
|
|
1571
|
-
var searchPartiesSchema =
|
|
1572
|
-
q:
|
|
1573
|
-
embed:
|
|
1574
|
-
page:
|
|
1575
|
-
perPage:
|
|
1647
|
+
var searchPartiesSchema = z7.object({
|
|
1648
|
+
q: z7.string().optional().describe("Free-text search query"),
|
|
1649
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1650
|
+
page: z7.number().int().positive().optional().default(1),
|
|
1651
|
+
perPage: z7.number().int().min(1).max(100).optional().default(25)
|
|
1576
1652
|
});
|
|
1577
1653
|
async function searchParties(input) {
|
|
1578
1654
|
const path = input.q ? "/parties/search" : "/parties";
|
|
@@ -1584,9 +1660,9 @@ async function searchParties(input) {
|
|
|
1584
1660
|
});
|
|
1585
1661
|
return { ...data, nextPage };
|
|
1586
1662
|
}
|
|
1587
|
-
var getPartySchema =
|
|
1588
|
-
id:
|
|
1589
|
-
embed:
|
|
1663
|
+
var getPartySchema = z7.object({
|
|
1664
|
+
id: positiveId.describe("Party ID"),
|
|
1665
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1590
1666
|
});
|
|
1591
1667
|
async function getParty(input) {
|
|
1592
1668
|
const { data } = await capsuleGet(`/parties/${input.id}`, {
|
|
@@ -1594,11 +1670,11 @@ async function getParty(input) {
|
|
|
1594
1670
|
});
|
|
1595
1671
|
return data;
|
|
1596
1672
|
}
|
|
1597
|
-
var getPartiesSchema =
|
|
1598
|
-
ids:
|
|
1673
|
+
var getPartiesSchema = z7.object({
|
|
1674
|
+
ids: z7.array(positiveId).min(1).max(50).describe(
|
|
1599
1675
|
"Array of party IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel. Result shape is identical regardless of input size."
|
|
1600
1676
|
),
|
|
1601
|
-
embed:
|
|
1677
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1602
1678
|
});
|
|
1603
1679
|
async function getParties(input) {
|
|
1604
1680
|
const { ids, embed } = input;
|
|
@@ -1616,10 +1692,10 @@ async function getParties(input) {
|
|
|
1616
1692
|
);
|
|
1617
1693
|
return { parties: responses.flatMap((r) => r.data.parties) };
|
|
1618
1694
|
}
|
|
1619
|
-
var listPartyOpportunitiesSchema =
|
|
1620
|
-
partyId:
|
|
1621
|
-
page:
|
|
1622
|
-
perPage:
|
|
1695
|
+
var listPartyOpportunitiesSchema = z7.object({
|
|
1696
|
+
partyId: positiveId,
|
|
1697
|
+
page: z7.number().int().positive().optional().default(1),
|
|
1698
|
+
perPage: z7.number().int().min(1).max(100).optional().default(25)
|
|
1623
1699
|
});
|
|
1624
1700
|
async function listPartyOpportunities(input) {
|
|
1625
1701
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -1628,10 +1704,10 @@ async function listPartyOpportunities(input) {
|
|
|
1628
1704
|
);
|
|
1629
1705
|
return { ...data, nextPage };
|
|
1630
1706
|
}
|
|
1631
|
-
var listPartyProjectsSchema =
|
|
1632
|
-
partyId:
|
|
1633
|
-
page:
|
|
1634
|
-
perPage:
|
|
1707
|
+
var listPartyProjectsSchema = z7.object({
|
|
1708
|
+
partyId: positiveId,
|
|
1709
|
+
page: z7.number().int().positive().optional().default(1),
|
|
1710
|
+
perPage: z7.number().int().min(1).max(100).optional().default(25)
|
|
1635
1711
|
});
|
|
1636
1712
|
async function listPartyProjects(input) {
|
|
1637
1713
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -1641,50 +1717,50 @@ async function listPartyProjects(input) {
|
|
|
1641
1717
|
return { ...data, nextPage };
|
|
1642
1718
|
}
|
|
1643
1719
|
var PartyWriteBaseSchema = {
|
|
1644
|
-
about:
|
|
1645
|
-
emailAddresses:
|
|
1720
|
+
about: z7.string().optional(),
|
|
1721
|
+
emailAddresses: z7.array(EmailAddressSchema).optional().describe(
|
|
1646
1722
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_email_address and remove_party_email_address_by_id. Passing `[]` here is a silent no-op (does not clear the list and does not advance updatedAt)."
|
|
1647
1723
|
),
|
|
1648
|
-
phoneNumbers:
|
|
1724
|
+
phoneNumbers: z7.array(PhoneNumberSchema).optional().describe(
|
|
1649
1725
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_phone_number and remove_party_phone_number_by_id."
|
|
1650
1726
|
),
|
|
1651
|
-
addresses:
|
|
1727
|
+
addresses: z7.array(AddressSchema).optional().describe(
|
|
1652
1728
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_address and remove_party_address_by_id. The `country` field is mapped through Capsule's country dictionary \u2014 see `add_party_address.country` for the dictionary edges (small canonical-English-name list; inputs not in the dictionary are REJECTED with 422, not silently dropped)."
|
|
1653
1729
|
),
|
|
1654
|
-
websites:
|
|
1730
|
+
websites: z7.array(WebsiteSchema).optional().describe(
|
|
1655
1731
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_website and remove_party_website_by_id."
|
|
1656
1732
|
),
|
|
1657
|
-
ownerId:
|
|
1733
|
+
ownerId: positiveId.optional().describe(
|
|
1658
1734
|
"Assign to user ID. On create_party, defaults to the API-token owner when omitted. Once set, this connector cannot clear the owner back to null \u2014 use Capsule's web UI for that. Discover IDs via list_users."
|
|
1659
1735
|
)
|
|
1660
1736
|
};
|
|
1661
|
-
var createPartySchema =
|
|
1662
|
-
type:
|
|
1737
|
+
var createPartySchema = z7.object({
|
|
1738
|
+
type: z7.enum(["person", "organisation"]),
|
|
1663
1739
|
// person
|
|
1664
|
-
firstName:
|
|
1665
|
-
lastName:
|
|
1666
|
-
title:
|
|
1667
|
-
jobTitle:
|
|
1668
|
-
organisationId:
|
|
1740
|
+
firstName: z7.string().optional(),
|
|
1741
|
+
lastName: z7.string().optional(),
|
|
1742
|
+
title: z7.string().optional(),
|
|
1743
|
+
jobTitle: z7.string().optional(),
|
|
1744
|
+
organisationId: positiveId.optional().describe("Link person to an existing organisation ID"),
|
|
1669
1745
|
// organisation
|
|
1670
|
-
name:
|
|
1746
|
+
name: z7.string().optional(),
|
|
1671
1747
|
...PartyWriteBaseSchema
|
|
1672
1748
|
});
|
|
1673
1749
|
async function createParty(input) {
|
|
1674
1750
|
const { ownerId, organisationId, ...rest } = input;
|
|
1675
1751
|
const body = { ...rest };
|
|
1676
|
-
|
|
1677
|
-
|
|
1752
|
+
setRef(body, "owner", ownerId);
|
|
1753
|
+
setRef(body, "organisation", organisationId);
|
|
1678
1754
|
return capsulePost("/parties", { party: body });
|
|
1679
1755
|
}
|
|
1680
|
-
var updatePartySchema =
|
|
1681
|
-
id:
|
|
1682
|
-
firstName:
|
|
1683
|
-
lastName:
|
|
1684
|
-
title:
|
|
1685
|
-
jobTitle:
|
|
1686
|
-
name:
|
|
1687
|
-
fields:
|
|
1756
|
+
var updatePartySchema = z7.object({
|
|
1757
|
+
id: positiveId,
|
|
1758
|
+
firstName: z7.string().optional(),
|
|
1759
|
+
lastName: z7.string().optional(),
|
|
1760
|
+
title: z7.string().optional(),
|
|
1761
|
+
jobTitle: z7.string().optional(),
|
|
1762
|
+
name: z7.string().optional(),
|
|
1763
|
+
fields: z7.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_party")),
|
|
1688
1764
|
...PartyWriteBaseSchema
|
|
1689
1765
|
});
|
|
1690
1766
|
async function updateParty(input) {
|
|
@@ -1693,39 +1769,26 @@ async function updateParty(input) {
|
|
|
1693
1769
|
for (const [k, v] of Object.entries(rest)) {
|
|
1694
1770
|
if (v !== void 0) body[k] = v;
|
|
1695
1771
|
}
|
|
1696
|
-
|
|
1772
|
+
setRef(body, "owner", ownerId);
|
|
1697
1773
|
const mappedFields = mapFieldsForBody(fields);
|
|
1698
1774
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1699
1775
|
return capsulePut(`/parties/${id}`, { party: body });
|
|
1700
1776
|
}
|
|
1701
|
-
var batchUpdatePartySchema =
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
)
|
|
1777
|
+
var { schema: batchUpdatePartySchema, handler: batchUpdateParty } = defineBatch({
|
|
1778
|
+
toolName: "batch_update_party",
|
|
1779
|
+
itemSchema: updatePartySchema,
|
|
1780
|
+
itemDescription: "Array of 1\u201350 update_party inputs. Each item is the same shape as a single update_party call \u2014 id is required, every other field is optional. Capped at 50 so a single tool call can't burn an outsized share of Capsule's hourly per-token rate budget (~4000 req/h).",
|
|
1781
|
+
itemHandler: updateParty
|
|
1705
1782
|
});
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
id: z4.number().int().positive(),
|
|
1711
|
-
confirm: confirmFlag().describe(
|
|
1712
|
-
"Must be set to true. Deletes the party AND all linked notes, tasks, opportunities, and projects (kases). Deleting an ORGANISATION does NOT delete people linked to it via organisationId \u2014 their `organisation` field is silently cleared to null and they survive as standalone records. Irreversible."
|
|
1713
|
-
)
|
|
1783
|
+
var { schema: deletePartySchema, handler: deleteParty } = defineDelete({
|
|
1784
|
+
toolName: "delete_party",
|
|
1785
|
+
pathPrefix: "/parties",
|
|
1786
|
+
confirmHint: "Must be set to true. Deletes the party AND all linked notes, tasks, opportunities, and projects (kases). Deleting an ORGANISATION does NOT delete people linked to it via organisationId \u2014 their `organisation` field is silently cleared to null and they survive as standalone records. Irreversible."
|
|
1714
1787
|
});
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
return idempotent(
|
|
1720
|
-
() => capsuleDelete(`/parties/${input.id}`),
|
|
1721
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1722
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1723
|
-
);
|
|
1724
|
-
}
|
|
1725
|
-
var addPartyEmailAddressSchema = z4.object({
|
|
1726
|
-
partyId: z4.number().int().positive(),
|
|
1727
|
-
address: z4.string().email(),
|
|
1728
|
-
type: z4.string().optional().describe("Free-form label, e.g. 'Work', 'Home'.")
|
|
1788
|
+
var addPartyEmailAddressSchema = z7.object({
|
|
1789
|
+
partyId: positiveId,
|
|
1790
|
+
address: z7.string().email(),
|
|
1791
|
+
type: z7.string().optional().describe("Free-form label, e.g. 'Work', 'Home'.")
|
|
1729
1792
|
});
|
|
1730
1793
|
async function addPartyEmailAddress(input) {
|
|
1731
1794
|
const { partyId, address, type } = input;
|
|
@@ -1735,9 +1798,9 @@ async function addPartyEmailAddress(input) {
|
|
|
1735
1798
|
party: { emailAddresses: [item] }
|
|
1736
1799
|
});
|
|
1737
1800
|
}
|
|
1738
|
-
var removePartyEmailAddressByIdSchema =
|
|
1739
|
-
partyId:
|
|
1740
|
-
emailAddressId:
|
|
1801
|
+
var removePartyEmailAddressByIdSchema = z7.object({
|
|
1802
|
+
partyId: positiveId,
|
|
1803
|
+
emailAddressId: positiveId.describe(
|
|
1741
1804
|
"Capsule's id for the email-address row. Read it from get_party (each entry in emailAddresses carries an id)."
|
|
1742
1805
|
)
|
|
1743
1806
|
});
|
|
@@ -1757,10 +1820,10 @@ async function removePartyEmailAddressById(input) {
|
|
|
1757
1820
|
() => ({ removed: true, alreadyRemoved: true, partyId, emailAddressId })
|
|
1758
1821
|
);
|
|
1759
1822
|
}
|
|
1760
|
-
var addPartyPhoneNumberSchema =
|
|
1761
|
-
partyId:
|
|
1762
|
-
number:
|
|
1763
|
-
type:
|
|
1823
|
+
var addPartyPhoneNumberSchema = z7.object({
|
|
1824
|
+
partyId: positiveId,
|
|
1825
|
+
number: z7.string().min(1),
|
|
1826
|
+
type: z7.string().optional().describe("Free-form label, e.g. 'Work', 'Mobile'.")
|
|
1764
1827
|
});
|
|
1765
1828
|
async function addPartyPhoneNumber(input) {
|
|
1766
1829
|
const { partyId, number, type } = input;
|
|
@@ -1770,9 +1833,9 @@ async function addPartyPhoneNumber(input) {
|
|
|
1770
1833
|
party: { phoneNumbers: [item] }
|
|
1771
1834
|
});
|
|
1772
1835
|
}
|
|
1773
|
-
var removePartyPhoneNumberByIdSchema =
|
|
1774
|
-
partyId:
|
|
1775
|
-
phoneNumberId:
|
|
1836
|
+
var removePartyPhoneNumberByIdSchema = z7.object({
|
|
1837
|
+
partyId: positiveId,
|
|
1838
|
+
phoneNumberId: positiveId.describe(
|
|
1776
1839
|
"Capsule's id for the phone-number row. Read it from get_party (each entry in phoneNumbers carries an id)."
|
|
1777
1840
|
)
|
|
1778
1841
|
});
|
|
@@ -1792,14 +1855,14 @@ async function removePartyPhoneNumberById(input) {
|
|
|
1792
1855
|
() => ({ removed: true, alreadyRemoved: true, partyId, phoneNumberId })
|
|
1793
1856
|
);
|
|
1794
1857
|
}
|
|
1795
|
-
var addPartyAddressSchema =
|
|
1796
|
-
partyId:
|
|
1797
|
-
street:
|
|
1798
|
-
city:
|
|
1799
|
-
state:
|
|
1800
|
-
country:
|
|
1801
|
-
zip:
|
|
1802
|
-
type:
|
|
1858
|
+
var addPartyAddressSchema = z7.object({
|
|
1859
|
+
partyId: positiveId,
|
|
1860
|
+
street: z7.string().optional(),
|
|
1861
|
+
city: z7.string().optional(),
|
|
1862
|
+
state: z7.string().optional(),
|
|
1863
|
+
country: z7.string().optional().describe(CountryDescription),
|
|
1864
|
+
zip: z7.string().optional(),
|
|
1865
|
+
type: z7.string().optional().describe("Free-form label, e.g. 'Office', 'Home'.")
|
|
1803
1866
|
});
|
|
1804
1867
|
async function addPartyAddress(input) {
|
|
1805
1868
|
const { partyId, ...rest } = input;
|
|
@@ -1811,9 +1874,9 @@ async function addPartyAddress(input) {
|
|
|
1811
1874
|
party: { addresses: [item] }
|
|
1812
1875
|
});
|
|
1813
1876
|
}
|
|
1814
|
-
var removePartyAddressByIdSchema =
|
|
1815
|
-
partyId:
|
|
1816
|
-
addressId:
|
|
1877
|
+
var removePartyAddressByIdSchema = z7.object({
|
|
1878
|
+
partyId: positiveId,
|
|
1879
|
+
addressId: positiveId.describe(
|
|
1817
1880
|
"Capsule's id for the address row. Read it from get_party (each entry in addresses carries an id)."
|
|
1818
1881
|
)
|
|
1819
1882
|
});
|
|
@@ -1833,9 +1896,9 @@ async function removePartyAddressById(input) {
|
|
|
1833
1896
|
() => ({ removed: true, alreadyRemoved: true, partyId, addressId })
|
|
1834
1897
|
);
|
|
1835
1898
|
}
|
|
1836
|
-
var addPartyWebsiteSchema =
|
|
1837
|
-
partyId:
|
|
1838
|
-
address:
|
|
1899
|
+
var addPartyWebsiteSchema = z7.object({
|
|
1900
|
+
partyId: positiveId,
|
|
1901
|
+
address: z7.string().min(1).describe(
|
|
1839
1902
|
"The website address. A URL when service='URL', or a handle (e.g. '@acmeco') for social services."
|
|
1840
1903
|
),
|
|
1841
1904
|
service: WebsiteServiceEnum.optional().describe("Defaults to 'URL' if omitted.")
|
|
@@ -1848,9 +1911,9 @@ async function addPartyWebsite(input) {
|
|
|
1848
1911
|
party: { websites: [item] }
|
|
1849
1912
|
});
|
|
1850
1913
|
}
|
|
1851
|
-
var removePartyWebsiteByIdSchema =
|
|
1852
|
-
partyId:
|
|
1853
|
-
websiteId:
|
|
1914
|
+
var removePartyWebsiteByIdSchema = z7.object({
|
|
1915
|
+
partyId: positiveId,
|
|
1916
|
+
websiteId: positiveId.describe(
|
|
1854
1917
|
"Capsule's id for the website row. Read it from get_party (each entry in websites carries an id)."
|
|
1855
1918
|
)
|
|
1856
1919
|
});
|
|
@@ -1872,20 +1935,32 @@ async function removePartyWebsiteById(input) {
|
|
|
1872
1935
|
}
|
|
1873
1936
|
|
|
1874
1937
|
// src/tools/opportunities.ts
|
|
1875
|
-
import { z as
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1938
|
+
import { z as z8 } from "zod";
|
|
1939
|
+
|
|
1940
|
+
// src/tools/preserve-refs.ts
|
|
1941
|
+
async function readEntityRefs(path, responseKey) {
|
|
1942
|
+
const { data } = await capsuleGet(path);
|
|
1943
|
+
const entity = data[responseKey];
|
|
1944
|
+
return {
|
|
1945
|
+
teamId: entity?.team?.id ?? void 0,
|
|
1946
|
+
stageId: entity?.stage?.id ?? void 0
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/tools/opportunities.ts
|
|
1951
|
+
var OpportunityValueSchema = z8.object({
|
|
1952
|
+
amount: z8.number().nonnegative(),
|
|
1953
|
+
currency: z8.string({
|
|
1879
1954
|
error: (iss) => iss.code === "invalid_type" && iss.input === void 0 ? "currency is required when amount is set (3-letter ISO 4217 code, e.g. 'USD', 'EUR', 'GBP')" : void 0
|
|
1880
1955
|
}).length(3).describe(
|
|
1881
1956
|
"ISO 4217 currency code (3 letters), e.g. 'GBP', 'USD', 'EUR'. Required when amount is set."
|
|
1882
1957
|
)
|
|
1883
1958
|
});
|
|
1884
|
-
var searchOpportunitiesSchema =
|
|
1885
|
-
q:
|
|
1886
|
-
embed:
|
|
1887
|
-
page:
|
|
1888
|
-
perPage:
|
|
1959
|
+
var searchOpportunitiesSchema = z8.object({
|
|
1960
|
+
q: z8.string().optional().describe("Free-text search query"),
|
|
1961
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1962
|
+
page: z8.number().int().positive().optional().default(1),
|
|
1963
|
+
perPage: z8.number().int().min(1).max(100).optional().default(25)
|
|
1889
1964
|
});
|
|
1890
1965
|
async function searchOpportunities(input) {
|
|
1891
1966
|
const path = input.q ? "/opportunities/search" : "/opportunities";
|
|
@@ -1897,9 +1972,9 @@ async function searchOpportunities(input) {
|
|
|
1897
1972
|
});
|
|
1898
1973
|
return { ...data, nextPage };
|
|
1899
1974
|
}
|
|
1900
|
-
var getOpportunitySchema =
|
|
1901
|
-
id:
|
|
1902
|
-
embed:
|
|
1975
|
+
var getOpportunitySchema = z8.object({
|
|
1976
|
+
id: positiveId,
|
|
1977
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1903
1978
|
});
|
|
1904
1979
|
async function getOpportunity(input) {
|
|
1905
1980
|
const { data } = await capsuleGet(`/opportunities/${input.id}`, {
|
|
@@ -1907,11 +1982,11 @@ async function getOpportunity(input) {
|
|
|
1907
1982
|
});
|
|
1908
1983
|
return data;
|
|
1909
1984
|
}
|
|
1910
|
-
var getOpportunitiesSchema =
|
|
1911
|
-
ids:
|
|
1985
|
+
var getOpportunitiesSchema = z8.object({
|
|
1986
|
+
ids: z8.array(positiveId).min(1).max(50).describe(
|
|
1912
1987
|
"Array of opportunity IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel."
|
|
1913
1988
|
),
|
|
1914
|
-
embed:
|
|
1989
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1915
1990
|
});
|
|
1916
1991
|
async function getOpportunities(input) {
|
|
1917
1992
|
const { ids, embed } = input;
|
|
@@ -1932,102 +2007,96 @@ async function getOpportunities(input) {
|
|
|
1932
2007
|
);
|
|
1933
2008
|
return { opportunities: responses.flatMap((r) => r.data.opportunities) };
|
|
1934
2009
|
}
|
|
1935
|
-
var createOpportunitySchema =
|
|
1936
|
-
name:
|
|
1937
|
-
partyId:
|
|
1938
|
-
milestoneId:
|
|
1939
|
-
"ID of the pipeline milestone to place this opportunity at. The milestone implicitly determines the pipeline \u2014 there is no separate pipelineId parameter. Discover via list_pipelines / list_milestones."
|
|
2010
|
+
var createOpportunitySchema = z8.object({
|
|
2011
|
+
name: z8.string().min(1),
|
|
2012
|
+
partyId: positiveId.describe("ID of the party this opportunity belongs to"),
|
|
2013
|
+
milestoneId: positiveId.describe(
|
|
2014
|
+
"ID of the pipeline milestone to place this opportunity at. The milestone implicitly determines the pipeline \u2014 there is no separate pipelineId parameter. Discover via list_pipelines / list_milestones. NOTE: some Capsule tenants configure **pipeline / milestone-reached automation rules** that mutate `owner` and/or `team` immediately after creation \u2014 e.g. an 'Assign to a Team' action that fires on entry to a specific milestone and has been observed to clear `owner` as an automation side-effect. If you observe a newly-created opp landing with `owner: null` despite passing `ownerId`, the cause is almost certainly a milestone automation on the destination pipeline rather than the connector. Documented workaround: follow `create_opportunity` with an immediate `batch_update_opportunity({items: [{id, ownerId, teamId}]})` carrying both fields \u2014 PUT does not re-fire milestone-reached triggers, so the owner sticks."
|
|
1940
2015
|
),
|
|
1941
|
-
description:
|
|
2016
|
+
description: z8.string().optional(),
|
|
1942
2017
|
value: OpportunityValueSchema.optional(),
|
|
1943
|
-
expectedCloseOn:
|
|
1944
|
-
probability:
|
|
1945
|
-
ownerId:
|
|
1946
|
-
"Assign to user ID. Defaults to the API-token owner when omitted \u2014 note that opportunities do NOT inherit owner from the linked party, even though one might expect it. Once set, this connector cannot clear the owner back to null (use Capsule's web UI). Discover IDs via list_users."
|
|
2018
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
2019
|
+
probability: z8.number().int().min(0).max(100).optional(),
|
|
2020
|
+
ownerId: positiveId.optional().describe(
|
|
2021
|
+
"Assign to user ID. Defaults to the API-token owner when omitted \u2014 note that opportunities do NOT inherit owner from the linked party, even though one might expect it. Once set, this connector cannot clear the owner back to null (use Capsule's web UI). Discover IDs via list_users. WARNING: tenant pipeline / milestone-reached automation can mutate this field post-create \u2014 see the `milestoneId` description for details and the chained-PUT workaround."
|
|
2022
|
+
),
|
|
2023
|
+
teamId: positiveId.optional().describe(
|
|
2024
|
+
"Assign to team ID (discover via list_teams). Independent from `ownerId` \u2014 setting one does NOT clear the other on create. Three ownership shapes are valid: owner alone, team alone, or owner+team (the owner must be a member of the team; users can belong to multiple teams \u2014 422 'owner is not a member of the team' otherwise)."
|
|
1947
2025
|
)
|
|
1948
2026
|
});
|
|
1949
2027
|
async function createOpportunity(input) {
|
|
1950
|
-
const { partyId, milestoneId, ownerId, ...rest } = input;
|
|
2028
|
+
const { partyId, milestoneId, ownerId, teamId, ...rest } = input;
|
|
1951
2029
|
const body = {
|
|
1952
2030
|
...rest,
|
|
1953
2031
|
party: { id: partyId },
|
|
1954
2032
|
milestone: { id: milestoneId }
|
|
1955
2033
|
};
|
|
1956
|
-
|
|
2034
|
+
setRef(body, "owner", ownerId);
|
|
2035
|
+
setRef(body, "team", teamId);
|
|
1957
2036
|
return capsulePost("/opportunities", { opportunity: body });
|
|
1958
2037
|
}
|
|
1959
|
-
var updateOpportunitySchema =
|
|
1960
|
-
id:
|
|
1961
|
-
name:
|
|
1962
|
-
milestoneId:
|
|
1963
|
-
"Move the opportunity to this milestone. Side effects depend on the target: closing milestones (Won/Lost) auto-set `closedOn` to today and `probability` to the milestone default (100/0), preserving `lastOpenMilestone` as the previous open stage; moving back to an open milestone clears `closedOn` and re-applies the milestone's default probability (Won/Lost is reversible \u2014 no separate reopen tool). WARNING: Capsule does NOT validate that the new milestone belongs to the opportunity's current pipeline. Passing a milestoneId from a different pipeline silently relocates the opportunity across pipelines, and `lastOpenMilestone` may then reference a milestone in the previous pipeline. Verify against the opportunity's current pipeline (read the opp first, list its pipeline's milestones via list_milestones) before passing a cross-pipeline id."
|
|
2038
|
+
var updateOpportunitySchema = z8.object({
|
|
2039
|
+
id: positiveId,
|
|
2040
|
+
name: z8.string().min(1).optional(),
|
|
2041
|
+
milestoneId: positiveId.optional().describe(
|
|
2042
|
+
"Move the opportunity to this milestone. Side effects depend on the target: closing milestones (Won/Lost) auto-set `closedOn` to today and `probability` to the milestone default (100/0), preserving `lastOpenMilestone` as the previous open stage; moving back to an open milestone clears `closedOn` and re-applies the milestone's default probability (Won/Lost is reversible \u2014 no separate reopen tool). WARNING: Capsule does NOT validate that the new milestone belongs to the opportunity's current pipeline. Passing a milestoneId from a different pipeline silently relocates the opportunity across pipelines, and `lastOpenMilestone` may then reference a milestone in the previous pipeline. Verify against the opportunity's current pipeline (read the opp first, list its pipeline's milestones via list_milestones) before passing a cross-pipeline id. NOTE: changing `milestoneId` can fire **pipeline / milestone-reached automations** that mutate `owner` / `team` on the destination milestone (same shape as `create_opportunity` \u2014 see its `milestoneId` description for the owner-clearing automation caveat). If a milestone-change-and-owner-set in the same call lands with `owner: null`, follow up with a second `update_opportunity` (or `batch_update_opportunity`) carrying both `ownerId` and `teamId` \u2014 milestone-reached triggers only fire on the transition, so a subsequent PUT preserves your values."
|
|
1964
2043
|
),
|
|
1965
|
-
description:
|
|
2044
|
+
description: z8.string().optional(),
|
|
1966
2045
|
value: OpportunityValueSchema.optional(),
|
|
1967
|
-
expectedCloseOn:
|
|
1968
|
-
probability:
|
|
2046
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
2047
|
+
probability: z8.number().int().min(0).max(100).optional().describe(
|
|
1969
2048
|
"Win probability 0\u2013100. On an open milestone this overrides the milestone's default probability. CANNOT be set in the same call as a closing milestone (Won/Lost) \u2014 Capsule processes the milestone change first, the opportunity becomes closed, then the probability update is rejected as edit-on-closed-opp with 422 'probability can be updated only for open opportunity'. To close an opportunity, leave probability out of the call: it auto-snaps to 100% (Won) or 0% (Lost)."
|
|
1970
2049
|
),
|
|
1971
|
-
lostReasonId:
|
|
2050
|
+
lostReasonId: positiveId.optional().describe(
|
|
1972
2051
|
"Reason the opportunity was lost. Only meaningful when transitioning to a Lost milestone \u2014 Capsule silently drops it for other milestones. Without this set, a connector-driven Lost-close leaves `lostReason: null`. Discover IDs via list_lostreasons."
|
|
1973
2052
|
),
|
|
1974
|
-
ownerId:
|
|
1975
|
-
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that."
|
|
2053
|
+
ownerId: positiveId.optional().describe(
|
|
2054
|
+
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that. When you supply `ownerId` and omit `teamId`, the connector fetches the opportunity's current team and includes it in the PUT body to preserve it across the owner change. Without this defensive read, Capsule's PUT would clear the existing team (see NOTES-ON-CAPSULE-API.md \xA727 \u2014 same asymmetric semantic as /kases). Supply `teamId` explicitly on the same call to change the team instead."
|
|
2055
|
+
),
|
|
2056
|
+
teamId: positiveId.nullable().optional().describe(
|
|
2057
|
+
"Reassign team: pass a team ID (discover via list_teams) to set, or `null` to unassign. Capsule preserves the existing owner across a team change (server-side), so `update_opportunity { teamId }` alone is safe \u2014 the owner is carried through. Owner must be a member of the new team or Capsule returns 422 'owner is not a member of the team'. Independent from `ownerId` \u2014 setting `teamId` does NOT clear the owner."
|
|
1976
2058
|
),
|
|
1977
|
-
fields:
|
|
2059
|
+
fields: z8.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_opportunity"))
|
|
1978
2060
|
});
|
|
1979
2061
|
async function updateOpportunity(input) {
|
|
1980
|
-
const { id, milestoneId, ownerId, lostReasonId, fields, ...rest } = input;
|
|
2062
|
+
const { id, milestoneId, ownerId, teamId, lostReasonId, fields, ...rest } = input;
|
|
1981
2063
|
const body = {};
|
|
1982
2064
|
for (const [k, v] of Object.entries(rest)) {
|
|
1983
2065
|
if (v !== void 0) body[k] = v;
|
|
1984
2066
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
if (
|
|
2067
|
+
setRef(body, "milestone", milestoneId);
|
|
2068
|
+
let resolvedTeamId = teamId;
|
|
2069
|
+
if (ownerId !== void 0 && teamId === void 0) {
|
|
2070
|
+
({ teamId: resolvedTeamId } = await readEntityRefs(`/opportunities/${id}`, "opportunity"));
|
|
2071
|
+
}
|
|
2072
|
+
setRef(body, "owner", ownerId);
|
|
2073
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
2074
|
+
setRef(body, "lostReason", lostReasonId);
|
|
1988
2075
|
const mappedFields = mapFieldsForBody(fields);
|
|
1989
2076
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1990
2077
|
return capsulePut(`/opportunities/${id}`, {
|
|
1991
2078
|
opportunity: body
|
|
1992
2079
|
});
|
|
1993
2080
|
}
|
|
1994
|
-
var batchUpdateOpportunitySchema =
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2081
|
+
var { schema: batchUpdateOpportunitySchema, handler: batchUpdateOpportunity } = defineBatch({
|
|
2082
|
+
toolName: "batch_update_opportunity",
|
|
2083
|
+
itemSchema: updateOpportunitySchema,
|
|
2084
|
+
itemDescription: "Array of 1\u201350 update_opportunity inputs. Each item is the same shape as a single update_opportunity call \u2014 id is required, every other field is optional. Capped at 50 so a single tool call can't burn an outsized share of Capsule's hourly per-token rate budget.",
|
|
2085
|
+
itemHandler: updateOpportunity
|
|
1998
2086
|
});
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
(item) => updateOpportunity(item),
|
|
2004
|
-
opts
|
|
2005
|
-
);
|
|
2006
|
-
}
|
|
2007
|
-
var deleteOpportunitySchema = z5.object({
|
|
2008
|
-
id: z5.number().int().positive(),
|
|
2009
|
-
confirm: confirmFlag().describe(
|
|
2010
|
-
"Must be set to true. Permanently deletes the opportunity. Irreversible."
|
|
2011
|
-
)
|
|
2087
|
+
var { schema: deleteOpportunitySchema, handler: deleteOpportunity } = defineDelete({
|
|
2088
|
+
toolName: "delete_opportunity",
|
|
2089
|
+
pathPrefix: "/opportunities",
|
|
2090
|
+
confirmHint: "Must be set to true. Permanently deletes the opportunity. Irreversible."
|
|
2012
2091
|
});
|
|
2013
|
-
async function deleteOpportunity(input) {
|
|
2014
|
-
if (input.confirm !== true) {
|
|
2015
|
-
throw new Error("delete_opportunity requires confirm: true");
|
|
2016
|
-
}
|
|
2017
|
-
return idempotent(
|
|
2018
|
-
() => capsuleDelete(`/opportunities/${input.id}`),
|
|
2019
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
2020
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
2021
|
-
);
|
|
2022
|
-
}
|
|
2023
2092
|
|
|
2024
2093
|
// src/tools/projects.ts
|
|
2025
|
-
import { z as
|
|
2026
|
-
var listProjectsSchema =
|
|
2027
|
-
status:
|
|
2028
|
-
embed:
|
|
2029
|
-
page:
|
|
2030
|
-
perPage:
|
|
2094
|
+
import { z as z9 } from "zod";
|
|
2095
|
+
var listProjectsSchema = z9.object({
|
|
2096
|
+
status: z9.enum(["OPEN", "CLOSED"]).optional(),
|
|
2097
|
+
embed: z9.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2098
|
+
page: z9.number().int().positive().optional().default(1),
|
|
2099
|
+
perPage: z9.number().int().min(1).max(100).optional().default(25)
|
|
2031
2100
|
});
|
|
2032
2101
|
async function listProjects(input) {
|
|
2033
2102
|
const { data, nextPage } = await capsuleGet("/kases", {
|
|
@@ -2038,9 +2107,9 @@ async function listProjects(input) {
|
|
|
2038
2107
|
});
|
|
2039
2108
|
return { ...data, nextPage };
|
|
2040
2109
|
}
|
|
2041
|
-
var getProjectSchema =
|
|
2042
|
-
id:
|
|
2043
|
-
embed:
|
|
2110
|
+
var getProjectSchema = z9.object({
|
|
2111
|
+
id: positiveId,
|
|
2112
|
+
embed: z9.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
2044
2113
|
});
|
|
2045
2114
|
async function getProject(input) {
|
|
2046
2115
|
const { data } = await capsuleGet(`/kases/${input.id}`, {
|
|
@@ -2048,11 +2117,11 @@ async function getProject(input) {
|
|
|
2048
2117
|
});
|
|
2049
2118
|
return data;
|
|
2050
2119
|
}
|
|
2051
|
-
var getProjectsSchema =
|
|
2052
|
-
ids:
|
|
2120
|
+
var getProjectsSchema = z9.object({
|
|
2121
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
2053
2122
|
"Array of project IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel."
|
|
2054
2123
|
),
|
|
2055
|
-
embed:
|
|
2124
|
+
embed: z9.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
2056
2125
|
});
|
|
2057
2126
|
async function getProjects(input) {
|
|
2058
2127
|
const { ids, embed } = input;
|
|
@@ -2070,21 +2139,21 @@ async function getProjects(input) {
|
|
|
2070
2139
|
);
|
|
2071
2140
|
return { kases: responses.flatMap((r) => r.data.kases) };
|
|
2072
2141
|
}
|
|
2073
|
-
var createProjectSchema =
|
|
2074
|
-
name:
|
|
2075
|
-
partyId:
|
|
2076
|
-
description:
|
|
2077
|
-
status:
|
|
2078
|
-
ownerId:
|
|
2142
|
+
var createProjectSchema = z9.object({
|
|
2143
|
+
name: z9.string().min(1),
|
|
2144
|
+
partyId: positiveId.describe("ID of the party linked to this project"),
|
|
2145
|
+
description: z9.string().optional(),
|
|
2146
|
+
status: z9.enum(["OPEN", "CLOSED"]).optional().describe("Defaults to OPEN when omitted."),
|
|
2147
|
+
ownerId: positiveId.optional().describe(
|
|
2079
2148
|
"Assign to user ID. Defaults to the API-token owner when omitted, same as create_party / create_opportunity / create_task. NOTE: some Capsule tenants configure board-level **automation rules** that mutate `owner` (and `team`) on project creation \u2014 e.g. an automation that clears `owner` when a project enters a particular board. If you observe a project landing with unexpected `owner: null` after a create_project with `ownerId`, check the target board's automation configuration. Capsule's API itself does not drop `ownerId` when `stageId` is also supplied."
|
|
2080
2149
|
),
|
|
2081
|
-
teamId:
|
|
2150
|
+
teamId: positiveId.optional().describe(
|
|
2082
2151
|
"Assign to team ID (discover via list_teams). Capsule projects must always have at least one of {owner, team} set \u2014 Capsule returns 422 'owner or team is required' otherwise. Three ownership shapes are valid: owner alone, team alone, or owner+team (the user must be a member of the team \u2014 users can belong to multiple teams; 422 'owner is not a member of the team' otherwise). Tenant-specific board automations may set the team field on project creation (e.g. 'when project enters board X, set team to T'). If you observe a team set despite omitting `teamId`, check the target board's automation rules."
|
|
2083
2152
|
),
|
|
2084
|
-
stageId:
|
|
2153
|
+
stageId: positiveId.optional().describe(
|
|
2085
2154
|
"Stage (board column) to place the project on. Discover IDs via list_stages \u2014 each stage belongs to one Board, so picking a stageId implicitly picks the board. If omitted, the project is created with no stage assignment (and won't appear on any board). NOTE: tenant-specific board automation rules may run on project creation and mutate `owner` / `team` fields. See `create_project.ownerId` / `create_project.teamId` for the automation caveat. Capsule's create endpoint itself preserves the `ownerId` / `teamId` you supply \u2014 any clearing you observe traces to board automations, not the API."
|
|
2086
2155
|
),
|
|
2087
|
-
expectedCloseOn:
|
|
2156
|
+
expectedCloseOn: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD")
|
|
2088
2157
|
});
|
|
2089
2158
|
async function createProject(input) {
|
|
2090
2159
|
const { partyId, ownerId, teamId, status, stageId, ...rest } = input;
|
|
@@ -2093,27 +2162,27 @@ async function createProject(input) {
|
|
|
2093
2162
|
status: status ?? "OPEN",
|
|
2094
2163
|
party: { id: partyId }
|
|
2095
2164
|
};
|
|
2096
|
-
|
|
2097
|
-
|
|
2165
|
+
setRef(body, "owner", ownerId);
|
|
2166
|
+
setRef(body, "team", teamId);
|
|
2098
2167
|
if (stageId) body["stage"] = stageId;
|
|
2099
2168
|
return capsulePost("/kases", { kase: body });
|
|
2100
2169
|
}
|
|
2101
|
-
var updateProjectSchema =
|
|
2102
|
-
id:
|
|
2103
|
-
name:
|
|
2104
|
-
description:
|
|
2105
|
-
status:
|
|
2106
|
-
ownerId:
|
|
2170
|
+
var updateProjectSchema = z9.object({
|
|
2171
|
+
id: positiveId,
|
|
2172
|
+
name: z9.string().min(1).optional(),
|
|
2173
|
+
description: z9.string().optional(),
|
|
2174
|
+
status: z9.enum(["OPEN", "CLOSED"]).optional(),
|
|
2175
|
+
ownerId: positiveId.nullable().optional().describe(
|
|
2107
2176
|
"Reassign owner: pass a user ID to set, or `null` to unassign (matches the 'Unassign' option in Capsule's web UI). When you supply `ownerId` and omit `teamId` and/or `stageId`, the connector fetches the project's current omitted fields and includes them in the PUT body \u2014 this preserves them across the owner change (without it, Capsule's PUT would clear team; stage carry is defensive against the symmetric clear). Supply `teamId` and/or `stageId` explicitly on the same call to change them instead. `teamId: null` clears the team as part of an owner change. Constraints (Capsule enforces, 422 on violation): owner must be a member of the team if both are set; a project must always have at least one of {owner, team} set (cannot clear both)."
|
|
2108
2177
|
),
|
|
2109
|
-
teamId:
|
|
2178
|
+
teamId: positiveId.nullable().optional().describe(
|
|
2110
2179
|
"Reassign team: pass a team ID (discover via list_teams) to set, or `null` to unassign. Capsule preserves the existing owner across a team change (server-side), so `update_project { teamId }` alone is safe \u2014 the owner is carried through. Owner must be a member of the new team or Capsule returns 422 'owner is not a member of the team'. A project must always have at least one of {owner, team} set \u2014 `teamId: null` on a project whose owner is already null returns 422 'owner or team is required'."
|
|
2111
2180
|
),
|
|
2112
|
-
stageId:
|
|
2181
|
+
stageId: positiveId.optional().describe(
|
|
2113
2182
|
"Move the project to this stage (board column). Discover IDs via list_stages. Owner and team are preserved across stage-only updates (Capsule's PUT semantic). WARNING (cross-board): Capsule does NOT validate that the new stage belongs to the project's current board \u2014 passing a stageId from a different board silently relocates the project across boards. Team and other board-derived defaults are NOT updated to match the new board. Verify against the project's current board (read the project first, list its board's stages) before passing a cross-board id."
|
|
2114
2183
|
),
|
|
2115
|
-
expectedCloseOn:
|
|
2116
|
-
fields:
|
|
2184
|
+
expectedCloseOn: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
2185
|
+
fields: z9.array(CustomFieldWriteSchema).optional().describe(
|
|
2117
2186
|
fieldsArrayDescriptor("get_project") + " Project-specific: setting a field whose definition lives under a 'data tag' populates the row's internal tagId but does NOT auto-add the data tag to the project's tags array \u2014 use add_tag explicitly if you want it visible via embed=tags."
|
|
2118
2187
|
)
|
|
2119
2188
|
});
|
|
@@ -2126,55 +2195,38 @@ async function updateProject(input) {
|
|
|
2126
2195
|
let resolvedTeamId = teamId;
|
|
2127
2196
|
let resolvedStageId = stageId;
|
|
2128
2197
|
if (ownerId !== void 0 && (teamId === void 0 || stageId === void 0)) {
|
|
2129
|
-
const
|
|
2130
|
-
if (teamId === void 0)
|
|
2131
|
-
|
|
2132
|
-
}
|
|
2133
|
-
if (stageId === void 0) {
|
|
2134
|
-
resolvedStageId = data.kase?.stage?.id ?? void 0;
|
|
2135
|
-
}
|
|
2198
|
+
const current = await readEntityRefs(`/kases/${id}`, "kase");
|
|
2199
|
+
if (teamId === void 0) resolvedTeamId = current.teamId;
|
|
2200
|
+
if (stageId === void 0) resolvedStageId = current.stageId;
|
|
2136
2201
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
if (resolvedTeamId === null) body["team"] = null;
|
|
2140
|
-
else if (resolvedTeamId !== void 0) body["team"] = { id: resolvedTeamId };
|
|
2202
|
+
setNullableRef(body, "owner", ownerId);
|
|
2203
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
2141
2204
|
if (resolvedStageId) body["stage"] = resolvedStageId;
|
|
2142
2205
|
const mappedFields = mapFieldsForBody(fields);
|
|
2143
2206
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
2144
2207
|
return capsulePut(`/kases/${id}`, { kase: body });
|
|
2145
2208
|
}
|
|
2146
|
-
var deleteProjectSchema =
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
)
|
|
2209
|
+
var { schema: deleteProjectSchema, handler: deleteProject } = defineDelete({
|
|
2210
|
+
toolName: "delete_project",
|
|
2211
|
+
pathPrefix: "/kases",
|
|
2212
|
+
confirmHint: "Must be set to true. Permanently deletes the project (case). Consider update_project status='CLOSED' instead. Irreversible."
|
|
2151
2213
|
});
|
|
2152
|
-
async function deleteProject(input) {
|
|
2153
|
-
if (input.confirm !== true) {
|
|
2154
|
-
throw new Error("delete_project requires confirm: true");
|
|
2155
|
-
}
|
|
2156
|
-
return idempotent(
|
|
2157
|
-
() => capsuleDelete(`/kases/${input.id}`),
|
|
2158
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
2159
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
2160
|
-
);
|
|
2161
|
-
}
|
|
2162
2214
|
|
|
2163
2215
|
// src/tools/tasks.ts
|
|
2164
|
-
import { z as
|
|
2165
|
-
var listTasksSchema =
|
|
2216
|
+
import { z as z10 } from "zod";
|
|
2217
|
+
var listTasksSchema = z10.object({
|
|
2166
2218
|
// Note: Capsule has a third internal status `PENDING` (a task that's
|
|
2167
2219
|
// part of an active track but not yet "open"), but it can only be
|
|
2168
2220
|
// reached via track machinery — it is NOT directly settable by
|
|
2169
2221
|
// /tasks PUT, and a list filter for it returns the same as OPEN
|
|
2170
2222
|
// anyway. We expose only the two values that are actually filterable
|
|
2171
2223
|
// by the v2 API.
|
|
2172
|
-
status:
|
|
2224
|
+
status: z10.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
2173
2225
|
"Defaults to OPEN when omitted. Pass COMPLETED to filter to completed tasks, or 'OPEN' explicitly."
|
|
2174
2226
|
),
|
|
2175
|
-
ownerId:
|
|
2176
|
-
page:
|
|
2177
|
-
perPage:
|
|
2227
|
+
ownerId: positiveId.optional().describe("Filter to tasks owned by this user ID"),
|
|
2228
|
+
page: z10.number().int().positive().optional().default(1),
|
|
2229
|
+
perPage: z10.number().int().min(1).max(100).optional().default(25)
|
|
2178
2230
|
});
|
|
2179
2231
|
async function listTasks(input) {
|
|
2180
2232
|
const { data, nextPage } = await capsuleGet("/tasks", {
|
|
@@ -2188,15 +2240,15 @@ async function listTasks(input) {
|
|
|
2188
2240
|
});
|
|
2189
2241
|
return { ...data, nextPage };
|
|
2190
2242
|
}
|
|
2191
|
-
var getTaskSchema =
|
|
2192
|
-
id:
|
|
2243
|
+
var getTaskSchema = z10.object({
|
|
2244
|
+
id: positiveId.describe("Task ID")
|
|
2193
2245
|
});
|
|
2194
2246
|
async function getTask(input) {
|
|
2195
2247
|
const { data } = await capsuleGet(`/tasks/${input.id}`);
|
|
2196
2248
|
return data;
|
|
2197
2249
|
}
|
|
2198
|
-
var getTasksSchema =
|
|
2199
|
-
ids:
|
|
2250
|
+
var getTasksSchema = z10.object({
|
|
2251
|
+
ids: z10.array(positiveId).min(1).max(50).describe(
|
|
2200
2252
|
"Array of task IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel."
|
|
2201
2253
|
)
|
|
2202
2254
|
});
|
|
@@ -2212,17 +2264,17 @@ async function getTasks(input) {
|
|
|
2212
2264
|
);
|
|
2213
2265
|
return { tasks: responses.flatMap((r) => r.data.tasks) };
|
|
2214
2266
|
}
|
|
2215
|
-
var createTaskSchema =
|
|
2216
|
-
description:
|
|
2217
|
-
dueOn:
|
|
2218
|
-
dueTime:
|
|
2219
|
-
detail:
|
|
2220
|
-
ownerId:
|
|
2267
|
+
var createTaskSchema = z10.object({
|
|
2268
|
+
description: z10.string().min(1),
|
|
2269
|
+
dueOn: z10.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("YYYY-MM-DD"),
|
|
2270
|
+
dueTime: z10.string().regex(/^\d{2}:\d{2}$/).optional().describe("HH:MM in user's timezone"),
|
|
2271
|
+
detail: z10.string().optional(),
|
|
2272
|
+
ownerId: positiveId.optional().describe(
|
|
2221
2273
|
"Assign to user ID. Defaults to the API-token owner when omitted. Once set, this connector cannot clear the owner back to null \u2014 use Capsule's web UI for that."
|
|
2222
2274
|
),
|
|
2223
|
-
partyId:
|
|
2224
|
-
opportunityId:
|
|
2225
|
-
projectId:
|
|
2275
|
+
partyId: positiveId.optional().describe("Link task to a party (mutually exclusive with opportunityId/projectId)"),
|
|
2276
|
+
opportunityId: positiveId.optional().describe("Link task to an opportunity (mutually exclusive with partyId/projectId)"),
|
|
2277
|
+
projectId: positiveId.optional().describe("Link task to a project (mutually exclusive with partyId/opportunityId)")
|
|
2226
2278
|
});
|
|
2227
2279
|
async function createTask(input) {
|
|
2228
2280
|
const linked = [input.partyId, input.opportunityId, input.projectId].filter(Boolean);
|
|
@@ -2231,25 +2283,25 @@ async function createTask(input) {
|
|
|
2231
2283
|
}
|
|
2232
2284
|
const { ownerId, partyId, opportunityId, projectId, ...rest } = input;
|
|
2233
2285
|
const body = { ...rest };
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2286
|
+
setRef(body, "owner", ownerId);
|
|
2287
|
+
setRef(body, "party", partyId);
|
|
2288
|
+
setRef(body, "opportunity", opportunityId);
|
|
2289
|
+
setRef(body, "kase", projectId);
|
|
2238
2290
|
return capsulePost("/tasks", { task: body });
|
|
2239
2291
|
}
|
|
2240
|
-
var updateTaskSchema =
|
|
2241
|
-
id:
|
|
2242
|
-
description:
|
|
2243
|
-
dueOn:
|
|
2244
|
-
dueTime:
|
|
2245
|
-
detail:
|
|
2292
|
+
var updateTaskSchema = z10.object({
|
|
2293
|
+
id: positiveId,
|
|
2294
|
+
description: z10.string().min(1).optional(),
|
|
2295
|
+
dueOn: z10.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
2296
|
+
dueTime: z10.string().regex(/^\d{2}:\d{2}$/).optional().describe("HH:MM in user's timezone"),
|
|
2297
|
+
detail: z10.string().optional(),
|
|
2246
2298
|
// Capsule rejects direct sets of `PENDING` (which is a track-machinery
|
|
2247
2299
|
// internal state) with 422 "cannot set task status to PENDING".
|
|
2248
2300
|
// Only OPEN and COMPLETED are settable here.
|
|
2249
|
-
status:
|
|
2301
|
+
status: z10.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
2250
2302
|
"Set to OPEN or COMPLETED. (PENDING exists internally for track-driven tasks but cannot be set directly via this tool \u2014 Capsule rejects it.) Setting status: OPEN on an already-open task is a true no-op (does not advance updatedAt)."
|
|
2251
2303
|
),
|
|
2252
|
-
ownerId:
|
|
2304
|
+
ownerId: positiveId.optional().describe(
|
|
2253
2305
|
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that."
|
|
2254
2306
|
)
|
|
2255
2307
|
});
|
|
@@ -2259,51 +2311,40 @@ async function updateTask(input) {
|
|
|
2259
2311
|
for (const [k, v] of Object.entries(rest)) {
|
|
2260
2312
|
if (v !== void 0) body[k] = v;
|
|
2261
2313
|
}
|
|
2262
|
-
|
|
2314
|
+
setRef(body, "owner", ownerId);
|
|
2263
2315
|
return capsulePut(`/tasks/${id}`, { task: body });
|
|
2264
2316
|
}
|
|
2265
|
-
var completeTaskSchema =
|
|
2266
|
-
id:
|
|
2317
|
+
var completeTaskSchema = z10.object({
|
|
2318
|
+
id: positiveId
|
|
2267
2319
|
});
|
|
2268
2320
|
async function completeTask(input) {
|
|
2269
2321
|
return capsulePut(`/tasks/${input.id}`, {
|
|
2270
2322
|
task: { status: "COMPLETED" }
|
|
2271
2323
|
});
|
|
2272
2324
|
}
|
|
2273
|
-
var batchCompleteTaskSchema =
|
|
2274
|
-
ids:
|
|
2325
|
+
var batchCompleteTaskSchema = z10.object({
|
|
2326
|
+
ids: z10.array(positiveId).min(1).max(50).describe(
|
|
2275
2327
|
"Array of 1\u201350 task ids to mark COMPLETED in parallel. Each id resolves to one PUT /tasks/{id}; failures (e.g. 404 for a deleted task) surface per-item in the result array, the rest still complete. Capped at 50."
|
|
2276
2328
|
)
|
|
2277
2329
|
});
|
|
2278
2330
|
async function batchCompleteTask(input, opts = {}) {
|
|
2279
2331
|
return batchExecute("batch_complete_task", input.ids, (id) => completeTask({ id }), opts);
|
|
2280
2332
|
}
|
|
2281
|
-
var deleteTaskSchema =
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
)
|
|
2333
|
+
var { schema: deleteTaskSchema, handler: deleteTask } = defineDelete({
|
|
2334
|
+
toolName: "delete_task",
|
|
2335
|
+
pathPrefix: "/tasks",
|
|
2336
|
+
confirmHint: "Must be set to true. Permanently deletes the task. To mark done without losing history use complete_task. Irreversible."
|
|
2286
2337
|
});
|
|
2287
|
-
async function deleteTask(input) {
|
|
2288
|
-
if (input.confirm !== true) {
|
|
2289
|
-
throw new Error("delete_task requires confirm: true");
|
|
2290
|
-
}
|
|
2291
|
-
return idempotent(
|
|
2292
|
-
() => capsuleDelete(`/tasks/${input.id}`),
|
|
2293
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
2294
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
2295
|
-
);
|
|
2296
|
-
}
|
|
2297
2338
|
|
|
2298
2339
|
// src/tools/entries.ts
|
|
2299
|
-
import { z as
|
|
2340
|
+
import { z as z11 } from "zod";
|
|
2300
2341
|
var listEntriesPagination = {
|
|
2301
|
-
page:
|
|
2302
|
-
perPage:
|
|
2303
|
-
embed:
|
|
2342
|
+
page: z11.number().int().positive().optional().default(1),
|
|
2343
|
+
perPage: z11.number().int().min(1).max(100).optional().default(25),
|
|
2344
|
+
embed: z11.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
2304
2345
|
};
|
|
2305
|
-
var listPartyEntriesSchema =
|
|
2306
|
-
partyId:
|
|
2346
|
+
var listPartyEntriesSchema = z11.object({
|
|
2347
|
+
partyId: positiveId,
|
|
2307
2348
|
...listEntriesPagination
|
|
2308
2349
|
});
|
|
2309
2350
|
async function listPartyEntries(input) {
|
|
@@ -2313,8 +2354,8 @@ async function listPartyEntries(input) {
|
|
|
2313
2354
|
);
|
|
2314
2355
|
return { ...data, nextPage };
|
|
2315
2356
|
}
|
|
2316
|
-
var listOpportunityEntriesSchema =
|
|
2317
|
-
opportunityId:
|
|
2357
|
+
var listOpportunityEntriesSchema = z11.object({
|
|
2358
|
+
opportunityId: positiveId,
|
|
2318
2359
|
...listEntriesPagination
|
|
2319
2360
|
});
|
|
2320
2361
|
async function listOpportunityEntries(input) {
|
|
@@ -2324,8 +2365,8 @@ async function listOpportunityEntries(input) {
|
|
|
2324
2365
|
);
|
|
2325
2366
|
return { ...data, nextPage };
|
|
2326
2367
|
}
|
|
2327
|
-
var listProjectEntriesSchema =
|
|
2328
|
-
projectId:
|
|
2368
|
+
var listProjectEntriesSchema = z11.object({
|
|
2369
|
+
projectId: positiveId,
|
|
2329
2370
|
...listEntriesPagination
|
|
2330
2371
|
});
|
|
2331
2372
|
async function listProjectEntries(input) {
|
|
@@ -2335,9 +2376,9 @@ async function listProjectEntries(input) {
|
|
|
2335
2376
|
);
|
|
2336
2377
|
return { ...data, nextPage };
|
|
2337
2378
|
}
|
|
2338
|
-
var getEntrySchema =
|
|
2339
|
-
id:
|
|
2340
|
-
embed:
|
|
2379
|
+
var getEntrySchema = z11.object({
|
|
2380
|
+
id: positiveId,
|
|
2381
|
+
embed: z11.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
2341
2382
|
});
|
|
2342
2383
|
async function getEntry(input) {
|
|
2343
2384
|
const { data } = await capsuleGet(`/entries/${input.id}`, {
|
|
@@ -2345,7 +2386,7 @@ async function getEntry(input) {
|
|
|
2345
2386
|
});
|
|
2346
2387
|
return data;
|
|
2347
2388
|
}
|
|
2348
|
-
var listEntriesSchema =
|
|
2389
|
+
var listEntriesSchema = z11.object({
|
|
2349
2390
|
...listEntriesPagination
|
|
2350
2391
|
});
|
|
2351
2392
|
async function listEntries(input) {
|
|
@@ -2356,14 +2397,14 @@ async function listEntries(input) {
|
|
|
2356
2397
|
});
|
|
2357
2398
|
return { ...data, nextPage };
|
|
2358
2399
|
}
|
|
2359
|
-
var addNoteSchema =
|
|
2360
|
-
content:
|
|
2400
|
+
var addNoteSchema = z11.object({
|
|
2401
|
+
content: z11.string().min(1).describe(
|
|
2361
2402
|
"Note body text. Stored verbatim and treated as MARKDOWN \u2014 Capsule's web UI renders the markdown when displaying. Pass markdown source ('# Heading', '**bold**', '- bullet'), not HTML."
|
|
2362
2403
|
),
|
|
2363
|
-
partyId:
|
|
2364
|
-
opportunityId:
|
|
2365
|
-
projectId:
|
|
2366
|
-
entryAt:
|
|
2404
|
+
partyId: positiveId.optional().describe("Link note to a party (mutually exclusive with opportunityId/projectId)"),
|
|
2405
|
+
opportunityId: positiveId.optional().describe("Link note to an opportunity (mutually exclusive with partyId/projectId)"),
|
|
2406
|
+
projectId: positiveId.optional().describe("Link note to a project (mutually exclusive with partyId/opportunityId)"),
|
|
2407
|
+
entryAt: z11.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/).optional().describe(
|
|
2367
2408
|
"ISO-8601 timestamp for when this note actually happened (e.g. '2024-03-15T14:30:00Z'). Defaults to now. Use this for backdating historical notes when migrating from another system. `entryAt` is preserved across subsequent update_entry calls; only `updatedAt` advances on edits. Note attribution flows to the API-token owner \u2014 there is no way to record a note as authored by a different user via this connector (a `creatorId` parameter would enable audit-attribution spoofing on shared-connector deployments, so it is intentionally not exposed)."
|
|
2368
2409
|
)
|
|
2369
2410
|
});
|
|
@@ -2374,18 +2415,18 @@ async function addNote(input) {
|
|
|
2374
2415
|
throw new Error("Provide exactly one of partyId, opportunityId, or projectId");
|
|
2375
2416
|
}
|
|
2376
2417
|
const body = { type: "note", content };
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2418
|
+
setRef(body, "party", partyId);
|
|
2419
|
+
setRef(body, "opportunity", opportunityId);
|
|
2420
|
+
setRef(body, "kase", projectId);
|
|
2380
2421
|
if (entryAt !== void 0) body["entryAt"] = entryAt;
|
|
2381
2422
|
return capsulePost("/entries", { entry: body });
|
|
2382
2423
|
}
|
|
2383
|
-
var updateEntrySchema =
|
|
2384
|
-
id:
|
|
2385
|
-
content:
|
|
2424
|
+
var updateEntrySchema = z11.object({
|
|
2425
|
+
id: positiveId.describe("Entry ID to update"),
|
|
2426
|
+
content: z11.string().min(1).optional().describe(
|
|
2386
2427
|
"New body text for the entry. For notes, this is the markdown content; for emails, the body. Provide only if you want to change it."
|
|
2387
2428
|
),
|
|
2388
|
-
subject:
|
|
2429
|
+
subject: z11.string().optional().describe(
|
|
2389
2430
|
"New subject line. Mostly meaningful on email-type entries; on plain notes Capsule accepts the call (HTTP 200) but **does not store the subject and does not advance `updatedAt`** \u2014 a true no-op for inapplicable fields. `entryAt` (when the note was authored) is preserved across edits; `updatedAt` advances only when an applicable field actually changes. To sort/filter by 'when did this happen', use `entryAt`; for 'last touched', use `updatedAt`."
|
|
2390
2431
|
)
|
|
2391
2432
|
});
|
|
@@ -2399,30 +2440,20 @@ async function updateEntry(input) {
|
|
|
2399
2440
|
}
|
|
2400
2441
|
return capsulePut(`/entries/${id}`, { entry: body });
|
|
2401
2442
|
}
|
|
2402
|
-
var deleteEntrySchema =
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
)
|
|
2443
|
+
var { schema: deleteEntrySchema, handler: deleteEntry } = defineDelete({
|
|
2444
|
+
toolName: "delete_entry",
|
|
2445
|
+
pathPrefix: "/entries",
|
|
2446
|
+
confirmHint: "Must be set to true. Permanently deletes the entry \u2014 use this to remove a note from a party/opportunity/project. Irreversible.",
|
|
2447
|
+
idDescription: "Entry (note/email/task-record) ID"
|
|
2407
2448
|
});
|
|
2408
|
-
async function deleteEntry(input) {
|
|
2409
|
-
if (input.confirm !== true) {
|
|
2410
|
-
throw new Error("delete_entry requires confirm: true");
|
|
2411
|
-
}
|
|
2412
|
-
return idempotent(
|
|
2413
|
-
() => capsuleDelete(`/entries/${input.id}`),
|
|
2414
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
2415
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
2416
|
-
);
|
|
2417
|
-
}
|
|
2418
2449
|
|
|
2419
2450
|
// src/tools/pipelines.ts
|
|
2420
|
-
import { z as
|
|
2451
|
+
import { z as z12 } from "zod";
|
|
2421
2452
|
var paginationFields = {
|
|
2422
|
-
page:
|
|
2423
|
-
perPage:
|
|
2453
|
+
page: z12.number().int().positive().optional(),
|
|
2454
|
+
perPage: z12.number().int().min(1).max(100).optional()
|
|
2424
2455
|
};
|
|
2425
|
-
var listPipelinesSchema =
|
|
2456
|
+
var listPipelinesSchema = z12.object({ ...paginationFields });
|
|
2426
2457
|
async function listPipelines(input) {
|
|
2427
2458
|
const { data, nextPage } = await capsuleGetCached("/pipelines", {
|
|
2428
2459
|
page: input.page ?? 1,
|
|
@@ -2430,8 +2461,8 @@ async function listPipelines(input) {
|
|
|
2430
2461
|
});
|
|
2431
2462
|
return { ...data, nextPage };
|
|
2432
2463
|
}
|
|
2433
|
-
var listMilestonesSchema =
|
|
2434
|
-
pipelineId:
|
|
2464
|
+
var listMilestonesSchema = z12.object({
|
|
2465
|
+
pipelineId: positiveId,
|
|
2435
2466
|
...paginationFields
|
|
2436
2467
|
});
|
|
2437
2468
|
async function listMilestones(input) {
|
|
@@ -2443,12 +2474,12 @@ async function listMilestones(input) {
|
|
|
2443
2474
|
}
|
|
2444
2475
|
|
|
2445
2476
|
// src/tools/boards.ts
|
|
2446
|
-
import { z as
|
|
2477
|
+
import { z as z13 } from "zod";
|
|
2447
2478
|
var paginationFields2 = {
|
|
2448
|
-
page:
|
|
2449
|
-
perPage:
|
|
2479
|
+
page: z13.number().int().positive().optional(),
|
|
2480
|
+
perPage: z13.number().int().min(1).max(100).optional()
|
|
2450
2481
|
};
|
|
2451
|
-
var listBoardsSchema =
|
|
2482
|
+
var listBoardsSchema = z13.object({ ...paginationFields2 });
|
|
2452
2483
|
async function listBoards(input) {
|
|
2453
2484
|
const { data, nextPage } = await capsuleGetCached("/boards", {
|
|
2454
2485
|
page: input.page ?? 1,
|
|
@@ -2456,8 +2487,8 @@ async function listBoards(input) {
|
|
|
2456
2487
|
});
|
|
2457
2488
|
return { ...data, nextPage };
|
|
2458
2489
|
}
|
|
2459
|
-
var listStagesSchema =
|
|
2460
|
-
boardId:
|
|
2490
|
+
var listStagesSchema = z13.object({
|
|
2491
|
+
boardId: positiveId.optional().describe(
|
|
2461
2492
|
"Optional. If provided, returns only the stages defined on that specific board (uses /boards/{id}/stages). Omit to get all stages across all boards in one call."
|
|
2462
2493
|
),
|
|
2463
2494
|
...paginationFields2
|
|
@@ -2472,7 +2503,7 @@ async function listStages(input) {
|
|
|
2472
2503
|
}
|
|
2473
2504
|
|
|
2474
2505
|
// src/tools/tags.ts
|
|
2475
|
-
import { z as
|
|
2506
|
+
import { z as z14 } from "zod";
|
|
2476
2507
|
var TAG_LIST_PATH = {
|
|
2477
2508
|
parties: "/parties/tags",
|
|
2478
2509
|
opportunities: "/opportunities/tags",
|
|
@@ -2483,11 +2514,11 @@ var ENTITY_TO_WRAPPER = {
|
|
|
2483
2514
|
opportunities: "opportunity",
|
|
2484
2515
|
kases: "kase"
|
|
2485
2516
|
};
|
|
2486
|
-
var TagEntity =
|
|
2487
|
-
var listTagsSchema =
|
|
2488
|
-
entity:
|
|
2489
|
-
page:
|
|
2490
|
-
perPage:
|
|
2517
|
+
var TagEntity = z14.enum(["parties", "opportunities", "kases"]).describe("Which entity type. Use 'kases' for projects (Capsule's legacy path name).");
|
|
2518
|
+
var listTagsSchema = z14.object({
|
|
2519
|
+
entity: z14.enum(["parties", "opportunities", "kases"]).describe("The resource type to list tags for"),
|
|
2520
|
+
page: z14.number().int().positive().optional(),
|
|
2521
|
+
perPage: z14.number().int().min(1).max(100).optional()
|
|
2491
2522
|
});
|
|
2492
2523
|
async function listTags(input) {
|
|
2493
2524
|
const path = TAG_LIST_PATH[input.entity];
|
|
@@ -2497,10 +2528,10 @@ async function listTags(input) {
|
|
|
2497
2528
|
});
|
|
2498
2529
|
return { ...data, nextPage };
|
|
2499
2530
|
}
|
|
2500
|
-
var addTagSchema =
|
|
2531
|
+
var addTagSchema = z14.object({
|
|
2501
2532
|
entity: TagEntity,
|
|
2502
|
-
entityId:
|
|
2503
|
-
tagName:
|
|
2533
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2534
|
+
tagName: z14.string().min(1).describe(
|
|
2504
2535
|
"Name of the tag to attach. Capsule resolves by name: if a tag with this name already exists in the tenant it is attached to the entity; if not, Capsule creates the tag and attaches it. Names are tenant-global. Capsule matches case-INSENSITIVELY when resolving (so 'VIP' and 'vip' attach the same tag), preserving the canonical casing from whichever variant was created first. To ensure consistent casing in your tag list, call list_tags first and reuse the exact name from there. Idempotent \u2014 re-attaching an already-attached tag is harmless."
|
|
2505
2536
|
)
|
|
2506
2537
|
});
|
|
@@ -2513,10 +2544,10 @@ async function addTag(input) {
|
|
|
2513
2544
|
invalidateByPrefix(TAG_LIST_PATH[entity], "add_tag");
|
|
2514
2545
|
return result;
|
|
2515
2546
|
}
|
|
2516
|
-
var removeTagByIdSchema =
|
|
2547
|
+
var removeTagByIdSchema = z14.object({
|
|
2517
2548
|
entity: TagEntity,
|
|
2518
|
-
entityId:
|
|
2519
|
-
tagId:
|
|
2549
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2550
|
+
tagId: positiveId.describe(
|
|
2520
2551
|
"The tag's id. Read via get_party / get_opportunity / get_project with embed='tags' \u2014 each tag entry in the response has an `id` field. list_tags returns the same ids for the same tags, so either source works; reading via embed first is the safer pattern because it confirms the tag is actually attached to this entity before you try to remove it (otherwise Capsule returns 422 'tag not found to delete'). Removing detaches the tag from this entity only; the tag definition itself persists in the tenant for other entities that share it."
|
|
2521
2552
|
)
|
|
2522
2553
|
});
|
|
@@ -2544,28 +2575,24 @@ async function removeTagById(input) {
|
|
|
2544
2575
|
invalidateByPrefix(TAG_LIST_PATH[entity], "remove_tag_by_id");
|
|
2545
2576
|
return result;
|
|
2546
2577
|
}
|
|
2547
|
-
var batchAddTagSchema =
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2578
|
+
var { schema: batchAddTagSchema, handler: batchAddTag } = defineBatch({
|
|
2579
|
+
toolName: "batch_add_tag",
|
|
2580
|
+
itemSchema: addTagSchema,
|
|
2581
|
+
itemDescription: "Array of 1\u201350 add_tag inputs. Useful for mass-tagging \u2014 e.g. 'tag these 20 contacts as RSAC26'. Each item is the same shape as a single add_tag call. The list_tags cache is invalidated for each affected entity type. Capped at 50.",
|
|
2582
|
+
itemHandler: addTag
|
|
2551
2583
|
});
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
"Array of 1\u201350 remove_tag_by_id inputs. Each item is the same shape as a single remove_tag_by_id call. Detaches the tag from each specified entity; the tag definition itself persists in the tenant. Capped at 50."
|
|
2558
|
-
)
|
|
2584
|
+
var { schema: batchRemoveTagByIdSchema, handler: batchRemoveTagById } = defineBatch({
|
|
2585
|
+
toolName: "batch_remove_tag_by_id",
|
|
2586
|
+
itemSchema: removeTagByIdSchema,
|
|
2587
|
+
itemDescription: "Array of 1\u201350 remove_tag_by_id inputs. Each item is the same shape as a single remove_tag_by_id call. Detaches the tag from each specified entity; the tag definition itself persists in the tenant. Capped at 50.",
|
|
2588
|
+
itemHandler: removeTagById
|
|
2559
2589
|
});
|
|
2560
|
-
async function batchRemoveTagById(input, opts = {}) {
|
|
2561
|
-
return batchExecute("batch_remove_tag_by_id", input.items, (item) => removeTagById(item), opts);
|
|
2562
|
-
}
|
|
2563
2590
|
|
|
2564
2591
|
// src/tools/users.ts
|
|
2565
|
-
import { z as
|
|
2566
|
-
var listUsersSchema =
|
|
2567
|
-
page:
|
|
2568
|
-
perPage:
|
|
2592
|
+
import { z as z15 } from "zod";
|
|
2593
|
+
var listUsersSchema = z15.object({
|
|
2594
|
+
page: z15.number().int().positive().optional(),
|
|
2595
|
+
perPage: z15.number().int().min(1).max(100).optional()
|
|
2569
2596
|
});
|
|
2570
2597
|
async function listUsers(input) {
|
|
2571
2598
|
const { data, nextPage } = await capsuleGetCached("/users", {
|
|
@@ -2574,32 +2601,32 @@ async function listUsers(input) {
|
|
|
2574
2601
|
});
|
|
2575
2602
|
return { ...data, nextPage };
|
|
2576
2603
|
}
|
|
2577
|
-
var getCurrentUserSchema =
|
|
2604
|
+
var getCurrentUserSchema = z15.object({});
|
|
2578
2605
|
async function getCurrentUser(_input) {
|
|
2579
2606
|
const { data } = await capsuleGet("/users/current");
|
|
2580
2607
|
return data;
|
|
2581
2608
|
}
|
|
2582
2609
|
|
|
2583
2610
|
// src/tools/filters.ts
|
|
2584
|
-
import { z as
|
|
2585
|
-
var FilterConditionSchema =
|
|
2586
|
-
field:
|
|
2611
|
+
import { z as z16 } from "zod";
|
|
2612
|
+
var FilterConditionSchema = z16.object({
|
|
2613
|
+
field: z16.string().describe(
|
|
2587
2614
|
"The Capsule filter-side field name (these differ from response field names \u2014 e.g. response.createdAt is filter-side 'addedOn', response.lastContactedAt is filter-side 'lastContactedOn'). Common: 'addedOn' (date created), 'updatedOn' (date last modified), 'lastContactedOn' (parties only), 'name', 'tag', 'owner', 'team', 'type' (parties: person|organisation), 'milestone' (opportunities), 'status' (opp/project: OPEN|CLOSED), 'closedOn' (opp/project), 'expectedCloseOn' (opp/project), 'hasTags', 'hasEmailAddress' (parties), 'isOpen', 'isStale' (opportunities), 'custom:{fieldId}'. Full per-entity list: https://developer.capsulecrm.com/v2/reference/filters"
|
|
2588
2615
|
),
|
|
2589
|
-
operator:
|
|
2616
|
+
operator: z16.string().describe(
|
|
2590
2617
|
"The filter operator. Common: 'is', 'is not' (use value=null to test for null), 'contains', 'does not contain', 'is greater than', 'is less than', 'is within last' (date fields, value=integer days), 'is more than' (date fields, value=integer days ago), 'starts with', 'ends with'. Operator validity depends on the field's type."
|
|
2591
2618
|
),
|
|
2592
|
-
value:
|
|
2619
|
+
value: z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]).describe(
|
|
2593
2620
|
"The value to compare against. For 'is within last' on date fields, pass an integer number of days. For tag filters, pass the tag name (string) or tag id (number). For 'is not' null tests, pass null literally."
|
|
2594
2621
|
)
|
|
2595
2622
|
});
|
|
2596
|
-
var FilterInputSchema =
|
|
2597
|
-
conditions:
|
|
2623
|
+
var FilterInputSchema = z16.object({
|
|
2624
|
+
conditions: z16.array(FilterConditionSchema).min(1).describe(
|
|
2598
2625
|
"Array of filter conditions. All conditions are ANDed together. To get newest records, use a date condition like {field: 'addedOn', operator: 'is within last', value: 7} and pick the highest-id row from the result (Capsule IDs are monotonic)."
|
|
2599
2626
|
),
|
|
2600
|
-
embed:
|
|
2601
|
-
page:
|
|
2602
|
-
perPage:
|
|
2627
|
+
embed: z16.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2628
|
+
page: z16.number().int().positive().optional().default(1),
|
|
2629
|
+
perPage: z16.number().int().min(1).max(100).optional().default(25)
|
|
2603
2630
|
});
|
|
2604
2631
|
async function runFilter(entityPath, input) {
|
|
2605
2632
|
const { data, nextPage } = await capsuleSearch(
|
|
@@ -2630,12 +2657,12 @@ async function filterProjects(input) {
|
|
|
2630
2657
|
}
|
|
2631
2658
|
|
|
2632
2659
|
// src/tools/metadata.ts
|
|
2633
|
-
import { z as
|
|
2660
|
+
import { z as z17 } from "zod";
|
|
2634
2661
|
var paginationFields3 = {
|
|
2635
|
-
page:
|
|
2636
|
-
perPage:
|
|
2662
|
+
page: z17.number().int().positive().optional(),
|
|
2663
|
+
perPage: z17.number().int().min(1).max(100).optional().describe("Page size, max 100. Defaults to 100 for reference data.")
|
|
2637
2664
|
};
|
|
2638
|
-
var listTeamsSchema =
|
|
2665
|
+
var listTeamsSchema = z17.object({ ...paginationFields3 });
|
|
2639
2666
|
async function listTeams(input) {
|
|
2640
2667
|
const { data, nextPage } = await capsuleGetCached("/teams", {
|
|
2641
2668
|
page: input.page ?? 1,
|
|
@@ -2643,7 +2670,7 @@ async function listTeams(input) {
|
|
|
2643
2670
|
});
|
|
2644
2671
|
return { ...data, nextPage };
|
|
2645
2672
|
}
|
|
2646
|
-
var listLostReasonsSchema =
|
|
2673
|
+
var listLostReasonsSchema = z17.object({ ...paginationFields3 });
|
|
2647
2674
|
async function listLostReasons(input) {
|
|
2648
2675
|
const { data, nextPage } = await capsuleGetCached("/lostreasons", {
|
|
2649
2676
|
page: input.page ?? 1,
|
|
@@ -2651,7 +2678,7 @@ async function listLostReasons(input) {
|
|
|
2651
2678
|
});
|
|
2652
2679
|
return { ...data, nextPage };
|
|
2653
2680
|
}
|
|
2654
|
-
var listActivityTypesSchema =
|
|
2681
|
+
var listActivityTypesSchema = z17.object({ ...paginationFields3 });
|
|
2655
2682
|
async function listActivityTypes(input) {
|
|
2656
2683
|
const { data, nextPage } = await capsuleGetCached(
|
|
2657
2684
|
"/activitytypes",
|
|
@@ -2662,12 +2689,12 @@ async function listActivityTypes(input) {
|
|
|
2662
2689
|
);
|
|
2663
2690
|
return { ...data, nextPage };
|
|
2664
2691
|
}
|
|
2665
|
-
var getSiteSchema =
|
|
2692
|
+
var getSiteSchema = z17.object({});
|
|
2666
2693
|
async function getSite(_input) {
|
|
2667
2694
|
const { data } = await capsuleGetCached("/site");
|
|
2668
2695
|
return data;
|
|
2669
2696
|
}
|
|
2670
|
-
var listTrackDefinitionsSchema =
|
|
2697
|
+
var listTrackDefinitionsSchema = z17.object({ ...paginationFields3 });
|
|
2671
2698
|
async function listTrackDefinitions(input) {
|
|
2672
2699
|
const { data, nextPage } = await capsuleGetCached(
|
|
2673
2700
|
"/trackdefinitions",
|
|
@@ -2675,7 +2702,7 @@ async function listTrackDefinitions(input) {
|
|
|
2675
2702
|
);
|
|
2676
2703
|
return { ...data, nextPage };
|
|
2677
2704
|
}
|
|
2678
|
-
var listCategoriesSchema =
|
|
2705
|
+
var listCategoriesSchema = z17.object({ ...paginationFields3 });
|
|
2679
2706
|
async function listCategories(input) {
|
|
2680
2707
|
const { data, nextPage } = await capsuleGetCached("/categories", {
|
|
2681
2708
|
page: input.page ?? 1,
|
|
@@ -2683,7 +2710,7 @@ async function listCategories(input) {
|
|
|
2683
2710
|
});
|
|
2684
2711
|
return { ...data, nextPage };
|
|
2685
2712
|
}
|
|
2686
|
-
var listGoalsSchema =
|
|
2713
|
+
var listGoalsSchema = z17.object({ ...paginationFields3 });
|
|
2687
2714
|
async function listGoals(input) {
|
|
2688
2715
|
const { data, nextPage } = await capsuleGetCached("/goals", {
|
|
2689
2716
|
page: input.page ?? 1,
|
|
@@ -2693,14 +2720,14 @@ async function listGoals(input) {
|
|
|
2693
2720
|
}
|
|
2694
2721
|
|
|
2695
2722
|
// src/tools/audit.ts
|
|
2696
|
-
import { z as
|
|
2697
|
-
var listEmployeesSchema =
|
|
2698
|
-
partyId:
|
|
2723
|
+
import { z as z18 } from "zod";
|
|
2724
|
+
var listEmployeesSchema = z18.object({
|
|
2725
|
+
partyId: positiveId.describe(
|
|
2699
2726
|
"The organisation's party id. Returns the people whose `organisation` field links to this party."
|
|
2700
2727
|
),
|
|
2701
|
-
page:
|
|
2702
|
-
perPage:
|
|
2703
|
-
embed:
|
|
2728
|
+
page: z18.number().int().positive().optional().default(1),
|
|
2729
|
+
perPage: z18.number().int().min(1).max(100).optional().default(25),
|
|
2730
|
+
embed: z18.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
2704
2731
|
});
|
|
2705
2732
|
async function listEmployees(input) {
|
|
2706
2733
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2709,15 +2736,15 @@ async function listEmployees(input) {
|
|
|
2709
2736
|
);
|
|
2710
2737
|
return { ...data, nextPage };
|
|
2711
2738
|
}
|
|
2712
|
-
var DeletedSinceSchema =
|
|
2739
|
+
var DeletedSinceSchema = z18.string().describe(
|
|
2713
2740
|
"REQUIRED. ISO-8601 timestamp; only deletions on or after this point are returned. Example: '2026-01-01T00:00:00Z'."
|
|
2714
2741
|
);
|
|
2715
2742
|
var DeletedPagination = {
|
|
2716
2743
|
since: DeletedSinceSchema,
|
|
2717
|
-
page:
|
|
2718
|
-
perPage:
|
|
2744
|
+
page: z18.number().int().positive().optional().default(1),
|
|
2745
|
+
perPage: z18.number().int().min(1).max(100).optional().default(25)
|
|
2719
2746
|
};
|
|
2720
|
-
var listDeletedPartiesSchema =
|
|
2747
|
+
var listDeletedPartiesSchema = z18.object(DeletedPagination);
|
|
2721
2748
|
async function listDeletedParties(input) {
|
|
2722
2749
|
const { data, nextPage } = await capsuleGet("/parties/deleted", {
|
|
2723
2750
|
since: input.since,
|
|
@@ -2726,7 +2753,7 @@ async function listDeletedParties(input) {
|
|
|
2726
2753
|
});
|
|
2727
2754
|
return { ...data, nextPage };
|
|
2728
2755
|
}
|
|
2729
|
-
var listDeletedOpportunitiesSchema =
|
|
2756
|
+
var listDeletedOpportunitiesSchema = z18.object(DeletedPagination);
|
|
2730
2757
|
async function listDeletedOpportunities(input) {
|
|
2731
2758
|
const { data, nextPage } = await capsuleGet("/opportunities/deleted", {
|
|
2732
2759
|
since: input.since,
|
|
@@ -2735,7 +2762,7 @@ async function listDeletedOpportunities(input) {
|
|
|
2735
2762
|
});
|
|
2736
2763
|
return { ...data, nextPage };
|
|
2737
2764
|
}
|
|
2738
|
-
var listDeletedProjectsSchema =
|
|
2765
|
+
var listDeletedProjectsSchema = z18.object(DeletedPagination);
|
|
2739
2766
|
async function listDeletedProjects(input) {
|
|
2740
2767
|
const { data, nextPage } = await capsuleGet("/kases/deleted", {
|
|
2741
2768
|
since: input.since,
|
|
@@ -2746,14 +2773,14 @@ async function listDeletedProjects(input) {
|
|
|
2746
2773
|
}
|
|
2747
2774
|
|
|
2748
2775
|
// src/tools/relationships.ts
|
|
2749
|
-
import { z as
|
|
2750
|
-
var RelationshipEntity =
|
|
2751
|
-
var listAdditionalPartiesSchema =
|
|
2776
|
+
import { z as z19 } from "zod";
|
|
2777
|
+
var RelationshipEntity = z19.enum(["opportunities", "kases"]).describe("Which entity has the additional-party links. Use 'kases' for projects.");
|
|
2778
|
+
var listAdditionalPartiesSchema = z19.object({
|
|
2752
2779
|
entity: RelationshipEntity,
|
|
2753
|
-
entityId:
|
|
2754
|
-
embed:
|
|
2755
|
-
page:
|
|
2756
|
-
perPage:
|
|
2780
|
+
entityId: positiveId.describe("ID of the opportunity or project."),
|
|
2781
|
+
embed: z19.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2782
|
+
page: z19.number().int().positive().optional().default(1),
|
|
2783
|
+
perPage: z19.number().int().min(1).max(100).optional().default(25)
|
|
2757
2784
|
});
|
|
2758
2785
|
async function listAdditionalParties(input) {
|
|
2759
2786
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2762,10 +2789,12 @@ async function listAdditionalParties(input) {
|
|
|
2762
2789
|
);
|
|
2763
2790
|
return { ...data, nextPage };
|
|
2764
2791
|
}
|
|
2765
|
-
var addAdditionalPartySchema =
|
|
2792
|
+
var addAdditionalPartySchema = z19.object({
|
|
2766
2793
|
entity: RelationshipEntity,
|
|
2767
|
-
entityId:
|
|
2768
|
-
partyId:
|
|
2794
|
+
entityId: positiveId,
|
|
2795
|
+
partyId: positiveId.describe(
|
|
2796
|
+
"ID of the party (person or organisation) to link as an additional party."
|
|
2797
|
+
)
|
|
2769
2798
|
});
|
|
2770
2799
|
async function addAdditionalParty(input) {
|
|
2771
2800
|
try {
|
|
@@ -2793,10 +2822,10 @@ async function addAdditionalParty(input) {
|
|
|
2793
2822
|
throw err;
|
|
2794
2823
|
}
|
|
2795
2824
|
}
|
|
2796
|
-
var removeAdditionalPartySchema =
|
|
2825
|
+
var removeAdditionalPartySchema = z19.object({
|
|
2797
2826
|
entity: RelationshipEntity,
|
|
2798
|
-
entityId:
|
|
2799
|
-
partyId:
|
|
2827
|
+
entityId: positiveId,
|
|
2828
|
+
partyId: positiveId,
|
|
2800
2829
|
confirm: confirmFlag().describe(
|
|
2801
2830
|
"Must be set to true. Removes the link between the entity and the additional party. The party itself is not deleted. Reversible by re-adding the link."
|
|
2802
2831
|
)
|
|
@@ -2823,11 +2852,11 @@ async function removeAdditionalParty(input) {
|
|
|
2823
2852
|
})
|
|
2824
2853
|
);
|
|
2825
2854
|
}
|
|
2826
|
-
var listAssociatedProjectsSchema =
|
|
2827
|
-
opportunityId:
|
|
2828
|
-
embed:
|
|
2829
|
-
page:
|
|
2830
|
-
perPage:
|
|
2855
|
+
var listAssociatedProjectsSchema = z19.object({
|
|
2856
|
+
opportunityId: positiveId,
|
|
2857
|
+
embed: z19.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2858
|
+
page: z19.number().int().positive().optional().default(1),
|
|
2859
|
+
perPage: z19.number().int().min(1).max(100).optional().default(25)
|
|
2831
2860
|
});
|
|
2832
2861
|
async function listAssociatedProjects(input) {
|
|
2833
2862
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2838,9 +2867,9 @@ async function listAssociatedProjects(input) {
|
|
|
2838
2867
|
}
|
|
2839
2868
|
|
|
2840
2869
|
// src/tools/custom-fields.ts
|
|
2841
|
-
import { z as
|
|
2842
|
-
var CustomFieldEntity =
|
|
2843
|
-
var listCustomFieldsSchema =
|
|
2870
|
+
import { z as z20 } from "zod";
|
|
2871
|
+
var CustomFieldEntity = z20.enum(["parties", "opportunities", "kases"]).describe("Which entity type's custom field schema to inspect. Use 'kases' for projects.");
|
|
2872
|
+
var listCustomFieldsSchema = z20.object({
|
|
2844
2873
|
entity: CustomFieldEntity
|
|
2845
2874
|
});
|
|
2846
2875
|
async function listCustomFields(input) {
|
|
@@ -2849,9 +2878,9 @@ async function listCustomFields(input) {
|
|
|
2849
2878
|
);
|
|
2850
2879
|
return data;
|
|
2851
2880
|
}
|
|
2852
|
-
var getCustomFieldSchema =
|
|
2881
|
+
var getCustomFieldSchema = z20.object({
|
|
2853
2882
|
entity: CustomFieldEntity,
|
|
2854
|
-
fieldId:
|
|
2883
|
+
fieldId: positiveId.describe("Custom field definition id.")
|
|
2855
2884
|
});
|
|
2856
2885
|
async function getCustomField(input) {
|
|
2857
2886
|
const { data } = await capsuleGetCached(
|
|
@@ -2861,11 +2890,11 @@ async function getCustomField(input) {
|
|
|
2861
2890
|
}
|
|
2862
2891
|
|
|
2863
2892
|
// src/tools/tracks.ts
|
|
2864
|
-
import { z as
|
|
2865
|
-
var TrackEntity =
|
|
2866
|
-
var listEntityTracksSchema =
|
|
2893
|
+
import { z as z21 } from "zod";
|
|
2894
|
+
var TrackEntity = z21.enum(["parties", "opportunities", "kases"]).describe("Use 'kases' for projects.");
|
|
2895
|
+
var listEntityTracksSchema = z21.object({
|
|
2867
2896
|
entity: TrackEntity,
|
|
2868
|
-
entityId:
|
|
2897
|
+
entityId: positiveId
|
|
2869
2898
|
});
|
|
2870
2899
|
async function listEntityTracks(input) {
|
|
2871
2900
|
const { data } = await capsuleGet(
|
|
@@ -2873,20 +2902,20 @@ async function listEntityTracks(input) {
|
|
|
2873
2902
|
);
|
|
2874
2903
|
return data;
|
|
2875
2904
|
}
|
|
2876
|
-
var showTrackSchema =
|
|
2877
|
-
trackId:
|
|
2905
|
+
var showTrackSchema = z21.object({
|
|
2906
|
+
trackId: positiveId
|
|
2878
2907
|
});
|
|
2879
2908
|
async function showTrack(input) {
|
|
2880
2909
|
const { data } = await capsuleGet(`/tracks/${input.trackId}`);
|
|
2881
2910
|
return data;
|
|
2882
2911
|
}
|
|
2883
|
-
var applyTrackSchema =
|
|
2884
|
-
entity:
|
|
2885
|
-
entityId:
|
|
2886
|
-
trackDefinitionId:
|
|
2912
|
+
var applyTrackSchema = z21.object({
|
|
2913
|
+
entity: z21.enum(["opportunities", "kases"]).describe("Which entity to apply the track to. Use 'kases' for projects."),
|
|
2914
|
+
entityId: positiveId,
|
|
2915
|
+
trackDefinitionId: positiveId.describe(
|
|
2887
2916
|
"The trackDefinition to apply (from list_track_definitions). Auto-creates task definitions on the target entity per the track's rules."
|
|
2888
2917
|
),
|
|
2889
|
-
startDate:
|
|
2918
|
+
startDate: z21.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe(
|
|
2890
2919
|
"Optional ISO-8601 date (YYYY-MM-DD) the track should start from \u2014 drives task due-date calculations (each task's `dueOn` is computed as startDate + the track-definition's `daysAfter` offset). Defaults to today if omitted. Useful for scheduling a renewal-queue track against a future contract end-date, or backfilling tracks for historical projects."
|
|
2891
2920
|
)
|
|
2892
2921
|
});
|
|
@@ -2899,9 +2928,9 @@ async function applyTrack(input) {
|
|
|
2899
2928
|
if (input.startDate !== void 0) track["trackDateOn"] = input.startDate;
|
|
2900
2929
|
return capsulePost("/tracks", { track });
|
|
2901
2930
|
}
|
|
2902
|
-
var updateTrackSchema =
|
|
2903
|
-
trackId:
|
|
2904
|
-
fields:
|
|
2931
|
+
var updateTrackSchema = z21.object({
|
|
2932
|
+
trackId: positiveId,
|
|
2933
|
+
fields: z21.record(z21.string(), z21.unknown()).describe(
|
|
2905
2934
|
"Object of fields to update on the track. Capsule's PUT semantics are partial \u2014 only the fields you provide are changed. Common: { complete: true } to mark a track completed. Capsule rejects unknown keys; consult Capsule's docs for the full updatable set."
|
|
2906
2935
|
)
|
|
2907
2936
|
});
|
|
@@ -2913,8 +2942,8 @@ async function updateTrack(input) {
|
|
|
2913
2942
|
track: input.fields
|
|
2914
2943
|
});
|
|
2915
2944
|
}
|
|
2916
|
-
var removeTrackSchema =
|
|
2917
|
-
trackId:
|
|
2945
|
+
var removeTrackSchema = z21.object({
|
|
2946
|
+
trackId: positiveId,
|
|
2918
2947
|
confirm: confirmFlag().describe(
|
|
2919
2948
|
"Must be set to true. Removes the track instance from its entity. **Capsule also deletes the auto-tasks the track created when it was applied** \u2014 they go with the track and become unreachable (404 on GET /tasks/{id}, gone from list_tasks on the parent entity). If you need any of those tasks to outlive the track, copy their content into fresh tasks (or use the web UI) before calling remove_track."
|
|
2920
2949
|
)
|
|
@@ -2931,13 +2960,13 @@ async function removeTrack(input) {
|
|
|
2931
2960
|
}
|
|
2932
2961
|
|
|
2933
2962
|
// src/tools/attachments.ts
|
|
2934
|
-
import { z as
|
|
2963
|
+
import { z as z22 } from "zod";
|
|
2935
2964
|
var DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
2936
2965
|
var HARD_MAX_SIZE_BYTES = 25 * 1024 * 1024;
|
|
2937
2966
|
var HARD_MAX_BASE64_CHARS = Math.ceil(HARD_MAX_SIZE_BYTES / 3) * 4;
|
|
2938
|
-
var getAttachmentSchema =
|
|
2939
|
-
id:
|
|
2940
|
-
maxSizeBytes:
|
|
2967
|
+
var getAttachmentSchema = z22.object({
|
|
2968
|
+
id: positiveId.describe("Attachment ID."),
|
|
2969
|
+
maxSizeBytes: z22.number().int().positive().max(HARD_MAX_SIZE_BYTES).optional().describe(
|
|
2941
2970
|
`Refuse to return content over this size (default ${DEFAULT_MAX_SIZE_BYTES} bytes \u2248 5MB; max ${HARD_MAX_SIZE_BYTES} bytes \u2248 25MB). Files exceeding the cap return metadata only with a 'truncated: true' flag.`
|
|
2942
2971
|
)
|
|
2943
2972
|
});
|
|
@@ -2952,22 +2981,22 @@ async function getAttachment(input) {
|
|
|
2952
2981
|
}
|
|
2953
2982
|
return { contentType, buffer, sizeBytes };
|
|
2954
2983
|
}
|
|
2955
|
-
var uploadAttachmentSchema =
|
|
2956
|
-
filename:
|
|
2984
|
+
var uploadAttachmentSchema = z22.object({
|
|
2985
|
+
filename: z22.string().min(1).describe(
|
|
2957
2986
|
"Filename Capsule should record (e.g. 'contract.pdf'). Capsule does NOT validate consistency between filename, contentType, and the actual bytes \u2014 a typo in either is accepted and the file is stored as labelled."
|
|
2958
2987
|
),
|
|
2959
|
-
contentType:
|
|
2988
|
+
contentType: z22.string().min(1).describe(
|
|
2960
2989
|
"MIME type of the file (e.g. 'application/pdf', 'image/png', 'text/plain'). Trusted by Capsule verbatim; not cross-checked against `filename` or the actual bytes."
|
|
2961
2990
|
),
|
|
2962
|
-
dataBase64:
|
|
2991
|
+
dataBase64: z22.string().min(1).max(HARD_MAX_BASE64_CHARS).describe(
|
|
2963
2992
|
"File contents, base64-encoded. Decoded server-side and uploaded as the request body. Maximum 25 MB per attachment (Capsule's documented limit); the connector rejects oversized base64 before uploading. The inbound HTTP body limit is ~35 MB which leaves room for the base64 expansion of a 25 MB binary."
|
|
2964
2993
|
),
|
|
2965
|
-
content:
|
|
2994
|
+
content: z22.string().optional().describe(
|
|
2966
2995
|
"Body text for the note that will hold the attachment. Defaults to '[attachment]' if omitted."
|
|
2967
2996
|
),
|
|
2968
|
-
partyId:
|
|
2969
|
-
opportunityId:
|
|
2970
|
-
projectId:
|
|
2997
|
+
partyId: positiveId.optional().describe("Link the new note to a party (mutually exclusive with opportunityId / projectId)."),
|
|
2998
|
+
opportunityId: positiveId.optional(),
|
|
2999
|
+
projectId: positiveId.optional()
|
|
2971
3000
|
});
|
|
2972
3001
|
function isValidBase64(s) {
|
|
2973
3002
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s)) return false;
|
|
@@ -3017,23 +3046,23 @@ async function uploadAttachment(input) {
|
|
|
3017
3046
|
}
|
|
3018
3047
|
|
|
3019
3048
|
// src/tools/saved-filters.ts
|
|
3020
|
-
import { z as
|
|
3021
|
-
var EntitySchema =
|
|
3049
|
+
import { z as z23 } from "zod";
|
|
3050
|
+
var EntitySchema = z23.enum(["parties", "opportunities", "kases"]).describe(
|
|
3022
3051
|
"Which entity type the filter operates over. Use 'kases' for projects (Capsule's legacy name)."
|
|
3023
3052
|
);
|
|
3024
|
-
var listSavedFiltersSchema =
|
|
3053
|
+
var listSavedFiltersSchema = z23.object({
|
|
3025
3054
|
entity: EntitySchema
|
|
3026
3055
|
});
|
|
3027
3056
|
async function listSavedFilters(input) {
|
|
3028
3057
|
const { data } = await capsuleGetCached(`/${input.entity}/filters`);
|
|
3029
3058
|
return data;
|
|
3030
3059
|
}
|
|
3031
|
-
var runSavedFilterSchema =
|
|
3060
|
+
var runSavedFilterSchema = z23.object({
|
|
3032
3061
|
entity: EntitySchema,
|
|
3033
|
-
id:
|
|
3034
|
-
embed:
|
|
3035
|
-
page:
|
|
3036
|
-
perPage:
|
|
3062
|
+
id: positiveId.describe("The saved filter id (from list_saved_filters)."),
|
|
3063
|
+
embed: z23.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
3064
|
+
page: z23.number().int().positive().optional().default(1),
|
|
3065
|
+
perPage: z23.number().int().min(1).max(100).optional().default(25)
|
|
3037
3066
|
});
|
|
3038
3067
|
async function runSavedFilter(input) {
|
|
3039
3068
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -3051,7 +3080,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
3051
3080
|
const server = new McpServer(
|
|
3052
3081
|
{
|
|
3053
3082
|
name: "capsulemcp",
|
|
3054
|
-
version: "1.6.
|
|
3083
|
+
version: "1.6.2",
|
|
3055
3084
|
description: "Read and (optionally) modify Capsule CRM data \u2014 parties, opportunities, projects, tasks, timeline entries, pipelines, tags.",
|
|
3056
3085
|
websiteUrl: "https://github.com/soil-dev/capsulemcp",
|
|
3057
3086
|
icons: ICONS
|
|
@@ -3636,7 +3665,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
3636
3665
|
registerTool(
|
|
3637
3666
|
server,
|
|
3638
3667
|
"list_teams",
|
|
3639
|
-
"List all teams configured in the Capsule account. Useful as input for filter_* queries that scope by team, and for reporting. LIMITATION: returns team identity only (id, name, description, timestamps). Capsule's v2 API does not expose team\u2194user membership through any endpoint \u2014 `GET /teams/{id}/users` 404s, `embed=users` is silently ignored, and `GET /users/{id}` doesn't include a `teams` field. To determine whether a given user belongs to a given team, either check Capsule's web UI Team Membership page, or probe via `update_project { ownerId: U, teamId: T }` and read the response \u2014 422 'owner is not a member of the team' means U \u2209 T.",
|
|
3668
|
+
"List all teams configured in the Capsule account. Useful as input for filter_* queries that scope by team, and for reporting. LIMITATION: returns team identity only (id, name, description, timestamps). Capsule's v2 API does not expose team\u2194user membership through any endpoint \u2014 `GET /teams/{id}/users` 404s, `embed=users` is silently ignored, and `GET /users/{id}` doesn't include a `teams` field. To determine whether a given user belongs to a given team, either check Capsule's web UI Team Membership page, or probe via `update_project { ownerId: U, teamId: T }` / `batch_update_opportunity { items: [{ id: <any opp>, ownerId: U, teamId: T }] }` and read the response \u2014 422 'owner is not a member of the team' means U \u2209 T. Both probe paths apply the same membership constraint server-side.",
|
|
3640
3669
|
listTeamsSchema,
|
|
3641
3670
|
listTeams
|
|
3642
3671
|
);
|